The code presented in this section is available as a repo for cloning from https://gitlab.com/arosano/employeeProject.git.
In this section we shall look at another case. This time
adding models to make the database handling
easier. We shall upgrade our MongoDB handling by
introducing and using the module mongoose
over mongodb
the we have used so far.
The following example will
showcase inserting an employee, and a
user document into the Mongo
database via their models.
Mongo models are implemented via mongoose
schemas, reminiscent of JavaScript classes.
Before discussing the content, we prepare, as usual, with
$ npx express --view=pug --git employeeProject ... $ cd employeeProject $ mkdir models 4 mkdir controllers $ npm install $ git init $ git add . $ git commit -m 'initial commit præ festum'
The database intends to demonstrate the use of mongoose in a 1:N scenario with total participation with the additional purpose of demonstrating joins in the non relational MongoDB.
employeeProject/models/Department.js
const mongoose = require("mongoose");
const schema = mongoose.Schema({
name: {
type: String,
required: true,
unique: true
},
address: {
street: {
type: String,
required: true
},
no: {
type: Number,
required: true
},
place: String,
zip: {
type: Number,
required: true
},
town: {
type: String,
required: true
}
}
});
module.exports = mongoose.model("Department", schema, 'department');
employeeProject/models/Program.js
const mongoose = require("mongoose");
const autopopulate = require('mongoose-autopopulate');
const schema = mongoose.Schema({
name: {
type: String,
required: true,
unique: true
},
programIdent: {
type: Number,
required: true,
unique: true
},
duration: {
type: Number,
required: true
},
department: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Department',
required: true,
autopopulate: true
}
});
schema.plugin(autopopulate);
module.exports = mongoose.model("Program", schema, 'program');
employeeProject/models/Person.js
const mongoose = require("mongoose");
const autopopulate = require('mongoose-autopopulate');
const schema = mongoose.Schema({
cpr: {
type: String,
validate: {
validator: function(v) {
return /\d{6}-\d{4}/.test(v);
},
message: props => `${props.value} is not a valid CPR number!`
},
unique: true,
required: true
},
email: {
type: String,
validate: {
validator: function(v) {
return /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,9}/.test(v);
},
message: props => `${props.value} is not a valid email address!`
},
unique: true,
required: true
},
firstname: {
type: String,
required: true
},
middlename: {
type: String,
},
lastname: {
type: String,
required: true
},
password: {
type: String,
required: true
},
role: {
type: String,
enum: ['admin', 'other', 'pending'],
default: 'pending'
},
program: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Program',
required: true,
autopopulate: true
}
});
schema.plugin(autopopulate);
module.exports = mongoose.model("Person", schema, 'person');
employeeProject/models/Student.js
const mongoose = require("mongoose");
const autopopulate = require('mongoose-autopopulate');
const schema = mongoose.Schema({
cpr: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Person',
required: true,
autopopulate: true
},
inducted: {
type: Date,
required: true
},
profile: {
type: String,
enum: ['tap', 'student', 'faculty'],
default: 'student'
}
});
schema.plugin(autopopulate);
module.exports = mongoose.model("Student", schema, 'student');
First install the mongoose
module by issuing
npm i mongoose
on the CLI in the project directory. Then let us look at our exemplary code.
employeeProject/routes/index.js
var express = require('express');
var router = express.Router();
const TITLE = 'Academy Pattern Project';
const con = require('../controllers/controller');
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', {
title: TITLE,
subtitle: 'Front Page'
});
});
/* GET show departments */
router.get('/departments', async function(req, res, next) {
let departments = await con.getDepts({}, {sort: {title: 1}}); // read depts from db
res.render('showdepts', {
title: TITLE,
subtitle: 'Display Departments',
departments
});
});
/* GET show html form for departments */
router.get('/deptform', function(req, res, next) {
res.render('deptformv', {
title: TITLE,
subtitle: 'Department Entry Form'
});
});
/* POST handle form data for departments */
router.post('/deptform', function(req, res, next) {
con.postDept(req, res, next); // write department into db
res.redirect('/');
});
/* GET show programs */
router.get('/programs', async function(req, res, next) {
let programs = await con.getPrograms({}, {sort: {title: 1}}); // read programs from db
res.render('programs', {
title: TITLE,
subtitle: 'Display Programs',
programs
});
});
/* GET show html form for programs */
router.get('/program', async function(req, res, next) {
let departments = await con.getDepts({}); // read departments, programs are in them
res.render('program', {
title: TITLE,
subtitle: 'Department Entry Form',
departments
});
});
/* POST handle form data for programs */
router.post('/program', function(req, res, next) {
con.postProg(req, res, next); // write program into db
res.redirect('/');
});
module.exports = router;
employeeProject/controllers/controller.js
const mongoose = require("mongoose");
const Department = require("../models/Department");
const Program = require("../models/Program");
module.exports = {
getDepts: async function (que, sort) {
const depts = await Department.find(que, null, sort); // read
return depts;
},
postDept: async function (req) {
let address = {
street: req.body.street,
no: req.body.no,
place: req.body.place,
zip: req.body.zip,
town: req.body.town
};
let dept = new Department({ // create object in schema-format
name: req.body.name,
address: address
});
try {
let rc = await Department.create(dept);
return rc;
} catch (err) {
console.log(err)
}
},
getPrograms: async function (que, sort) {
const progs = await Program.find(que, null, sort); // read
return progs;
},
postProg: async function (req) {
let program = new Program ({
name: req.body.name,
programIdent: req.body.minid,
duration: req.body.duration,
department: req.body.department
});
try {
let rc = await Program.create(program);
return rc;
} catch (err) {
console.log(err)
}
}
}
In this part we shall use the designated user
router. Express introduces that for separation of concerns.
employeeProject/routes/users.js
var express = require('express');
var router = express.Router();
const TITLE = 'Academy Pattern Project';
const con = require('../controllers/userController');
const dep = require('../controllers/controller');
/* GET show people */
router.get('/show', async function(req, res, next) {
let people = await con.getPeople({}, {sort: {title: 1}}); // read people from db
res.render('people', {
title: TITLE,
subtitle: 'Display People',
people
});
});
/* GET show html form for people */
router.get('/register', async function(req, res, next) {
let programs = await dep.getPrograms({}); // read programs, people work in them
res.render('register', {
title: TITLE,
subtitle: 'Register People',
programs
});
});
/* POST handle form data for people */
router.post('/register', con.postPerson, con.postStudent, async function(req, res, next) {
let programs = await dep.getPrograms({}); // read programs, people work in them
res.render('register', {
title: TITLE,
subtitle: 'Register People',
programs
});
});
module.exports = router;
employeeProject/controllers/userController.js
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const Person = require("../models/Person");
const Student = require("../models/Student");
module.exports = {
getPeople: async function (que, sort) {
const people = await Student.find(que, null, sort); // read
console.log(JSON.stringify(people, null, 4));
return people;
},
getPerson: async function (que, sort) {
const departments = await Dept.find(que, null, sort); // read
return departments;
},
postPerson: async function (req, res, next) {
let hash = await bcrypt.hash(req.body.password, 10);
try {
let person = new Person({ // create object in schema-format
cpr: req.body.cpr,
email: req.body.email,
firstname: req.body.firstname,
middlename: req.body.middlename,
lastname: req.body.lastname,
password: hash,
program: req.body.program
});
await Person.create(person);
res.locals.id = person._id;
next();
} catch (err) {
let message;
if (err.errors.cpr)
message = err.errors.cpr.properties.message;
else (err.errors.email)
message = err.errors.email.properties.message;
res.render('register', {
title: TITLE,
subtitle: 'Register People',
programs
});
next(err);
}
},
getStudent: async function (que, sort) {
const students = await Dept.find(que, null, sort); // read
return students;
},
postStudent: async function (req, res, next) {
if (req.body.type !== 'student') {
next();
}
let student = new Student({ // create object in schema-format
cpr: res.locals.id,
inducted: req.body.inducted
});
try {
await Student.create(student);
next();
} catch (err) {
console.log(err);
next(err);
}
}
};
All the individual database activities require a connection to a database server, hence, written once, as a a singleton:
employeeProject/models/MongoHandler.js
/*
* MongoConnect as a Singleton
*/
const mongoose = require('mongoose');
const MongoParams = require('../config/MongoParams').MongoParams;
const PARAMS = {
useNewUrlParser: true,
useUnifiedTopology: true
};
class MongoConnection {
static conn = false;
static async mongoConnect() {
if (!MongoConnection.conn) {
try {
await mongoose.connect(MongoParams.CONSTR, PARAMS);
MongoConnection.conn = mongoose.connection;
console.log(`Connected to mongo server`);
} catch (err) {
console.log('something mongo???' + err);
}
}
return MongoConnection.conn
}
}
module.exports = MongoConnection;
with these params
employeeProject/config/MongoParams.js
const DBS = process.env.DBS;
const DBN = process.env.DBN;
module.exports = {
MongoParams: {
CONSTR: `mongodb://${DBS}:27017/${DBN}`
}
};
In this part we shall use the designated user
router. Express introduces that for separation of concerns. This is a fragment.
employeeProject/views/layout.pug
doctype html
html
head
meta(charset='utf-8')
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
script(src='/javascripts/page.js')
body
header
nav
h1= title
ul
li
a(href='/') Home
li
a(href='/departments') Departments
li
a(href='/deptform') Reg Departments
li
a(href='/programs') Programs
li
a(href='/program') Reg Programs
li
a(href='/users/show') People
li
a(href='/users/register') Reg People
block content
employeeProject/views/people.pug
extends layout
block content
main.otherpage
aside
section
h2= subtitle
table.disp
tr
th Email
th Name
th Role
th Program
th Department
each pers in people
tr
td #{pers.cpr.email}
td #{pers.cpr.firstname} #{pers.cpr.middlename} #{pers.cpr.lastname}
td #{pers.cpr.role}
td #{pers.cpr.program.name}
td #{pers.cpr.program.department.name}
aside
include footer.pug
employeeProject/views/register.pug
extends layout
block content
main.otherpage
aside
section
h2= subtitle
form(id='registerForm' action='/users/register' method='post')
table
tr
td.col_l CPR
td
input(type='text' name='cpr' placeholder='ddmmyyyy-ssss' minlength='11' required)
tr
td.col_l Email
td
input(type='email' name='email' placeholder='a@b.cc' required)
tr
td.col_l User, First Name
td
input(type='text' name='firstname' placeholder='firstname' required)
tr
td.col_l User, Middle Name
td
input(type='text' name='middlename' placeholder='middlename')
tr
td.col_l User, Last Name
td
input(type='text' name='lastname' placeholder='lastname' required)
tr
td.col_l Induction Date
td
input(type='Date' name='inducted' required)
tr
td.col_l Password
td
input(type='password' id='password' name='password' placeholder='password, min 16 chars' minlength='16' required)
tr
td.col_l Password Again
td
input(type='password' name='password1' placeholder='password, must match the above' minlength='16' required)
tr
td.col_l Program
td
select(name='program' required)
each prog in programs
option(value=prog._id) #{prog.name}
tr
td.col_l Person Type
td
select(name='type' required)
each thing in ['student']
option #{thing}
tr
td
td
input(type='submit' value='Go')
aside
include footer.pug
employeeProject/public/javascripts/page.js
'use strict';
const $ = function (nml) { return document.getElementById(nml); };
const validate = function (e) { // validation example, if not alike, prevent submission
if ($('registerForm').password.value !== $('registerForm').password1.value) {
e.preventDefault();
window.alert('Two entered passwords do not match');
console.log($('registerForm').password+':'+$('registerForm').password1);
$('password').select();
return false;
}
};
const init = function () {
if ($('registerForm')) { // looking for particular form, if found setup validation
$('registerForm').addEventListener('submit', validate);
}
};
window.addEventListener('load', init);