### Repository
[https://github.com/nodejs/node](https://github.com/nodejs/node)
#### What Will I Learn
The codebase for this tutorial is based on [MEANie](https://github.com/cornflourblue/meanie) an open source content management system by [Jason Watmore](http://jasonwatmore.com/ ).
This is the fourth tutorial in the series of tutorials on building a content management system using the MEAN technology.
In the first three tutorials we created the backend server plus all helper and controller modules for the application.
In this tutorial we are going to work on creating the database services for the application.
We will create the following service modules in this tutorial
1. Page Service
2. Post Service
3. Redirect Service
4. User Service
N.B;- LINK TO THE EARLIER TUTORIALS IN THIS SERIES CAN BE FOUND AT THE END OF THIS POST
### Requirements
- [NodeJS and NPM](https://nodejs.org/en/download/package-manager/),
- [Angular](https://angular.io/)
- [MongoDB](https://www.mongodb.com/ )
- Text Editor
### Difficulty
- Intermediate
### Tutorial Contents
In the server directory create a new folder `services` which will contain all service modules for this application.
#### Page Service
In the created `services` folder add a new file `page.service.js` which will serve as the service module for the page controller module.
In the file we have
```
var config = require('config.json');
var _ = require('lodash');
var Q = require('q');
var slugify = require('helpers/slugify');
var mongo = require('mongoskin');
var db = mongo.db(config.connectionString, { native_parser: true });
db.bind('pages');
var service = {};
service.getAll = getAll;
service.getBySlug = getBySlug;
service.getById = getById;
service.create = create;
service.update = update;
service.delete = _delete;
module.exports = service;
function getAll() {
var deferred = Q.defer();
db.pages.find().toArray(function (err, pages) {
if (err) deferred.reject(err.name + ': ' + err.message);
pages = _.sortBy(pages, function (p) { return p.title.toLowerCase(); });
deferred.resolve(pages);
});
return deferred.promise;
}
function getBySlug(slug) {
var deferred = Q.defer();
db.pages.findOne({
slug: slug
}, function (err, page) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve(page);
});
return deferred.promise;
}
function getById(_id) {
var deferred = Q.defer();
db.pages.findById(_id, function (err, page) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve(page);
});
return deferred.promise;
}
function create(pageParam) {
var deferred = Q.defer();
// generate slug from title if empty
pageParam.slug = pageParam.slug || slugify(pageParam.title);
db.pages.insert(
pageParam,
function (err, doc) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
return deferred.promise;
}
function update(_id, pageParam) {
var deferred = Q.defer();
// generate slug from title if empty
pageParam.slug = pageParam.slug || slugify(pageParam.title);
// fields to update
var set = _.omit(pageParam, '_id');
db.pages.update(
{ _id: mongo.helper.toObjectID(_id) },
{ $set: set },
function (err, doc) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
return deferred.promise;
}
function _delete(_id) {
var deferred = Q.defer();
db.pages.remove(
{ _id: mongo.helper.toObjectID(_id) },
function (err) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
return deferred.promise;
}
```
This file uses the `q` library which is a promise module for JavaScript to handle promises.
The line`mongo.db(config.connectionString, { native_parser: true });` is used to connect to the MongoDB database server.
`db.bind(pages)` binds the service to the pages table in the database.
`service` creates a new object which holds all the methods required for this module as its properties.
`getAll()` accesses the pages table in the database and finds a list of all available page stored in the database then proceed to store it as an array.
`getBySlug()` accesses the pages table in the database and looks for one page from the list whose name matches the slug provided.
`getById(_id)` will look for the page matching the provided `id` in the pages table from the database.
`create()` generates a slug from the provided page title and uses that along with other parameters to create a new page and adds all values related to the newly created page to the database.
`update()` generates a slug from the provided page title then proceed to exclude fields from the database that does not require updating by editing the existing post field to add new values.
The function then insert the set values into the provided database fields.
`_delete()` removes an entire page field including the contents of the page from the database.
#### Post Service
Create another file in the `services` directory, `post.service.js`. This file will handle all service operations the database relating to blog posts.
The code for this file
```
var config = require('config.json');
var _ = require('lodash');
var Q = require('q');
var slugify = require('helpers/slugify');
var mongo = require('mongoskin');
var db = mongo.db(config.connectionString, { native_parser: true });
db.bind('posts');
var service = {};
service.getAll = getAll;
service.getByUrl = getByUrl;
service.getById = getById;
service.create = create;
service.update = update;
service.delete = _delete;
module.exports = service;
function getAll() {
var deferred = Q.defer();
db.posts.find().sort({ publishDate: -1 }).toArray(function (err, posts) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve(posts);
});
return deferred.promise;
}
function getByUrl(year, month, day, slug) {
var deferred = Q.defer();
db.posts.findOne({
publishDate: year + '-' + month + '-' + day,
slug: slug
}, function (err, post) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve(post);
});
return deferred.promise;
}
function getById(_id) {
var deferred = Q.defer();
db.posts.findById(_id, function (err, post) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve(post);
});
return deferred.promise;
}
function create(postParam) {
var deferred = Q.defer();
// generate slug from title if empty
postParam.slug = postParam.slug || slugify(postParam.title);
db.posts.insert(
postParam,
function (err, doc) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
return deferred.promise;
}
function update(_id, postParam) {
var deferred = Q.defer();
// generate slug from title if empty
postParam.slug = postParam.slug || slugify(postParam.title);
// fields to update
var set = _.omit(postParam, '_id');
db.posts.update(
{ _id: mongo.helper.toObjectID(_id) },
{ $set: set },
function (err, doc) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
return deferred.promise;
}
function _delete(_id) {
var deferred = Q.defer();
db.posts.remove(
{ _id: mongo.helper.toObjectID(_id) },
function (err) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
return deferred.promise;
}
```
`getAll()` looks through the posts table in the database to find all available posts, stores it in and stores all of the returned posts in an array.
`getByUrl()` looks through the posts in the database and finds the one post matching the the provided URL which is a combination of the publish date for the post and the slug associated with the post.
`getById()` looks the posts table in the database and finds the post matching the provided id.
`create()` generates a slug from the provided post title and uses that along with other parameters to create a new post and adds all values related to the newly created post to the database.
`update()` generates a slug from the provided post title then proceed to exclude fields from the database that does not require updating.
The function then insert the set values into the provided database fields by editing the existing post field to add new values.
`_delete()` removes an entire page field including the contents of the page from the database.
#### Redirect Service
Create a new file in the services directory `redirect.service.js`. It will handle all database communications for redirect operations in our application.
The code for the redirect service
```
var config = require('config.json');
var _ = require('lodash');
var Q = require('q');
var mongo = require('mongoskin');
var db = mongo.db(config.connectionString, { native_parser: true });
db.bind('redirects');
var service = {};
service.getAll = getAll;
service.getById = getById;
service.getByFrom = getByFrom;
service.create = create;
service.update = update;
service.delete = _delete;
module.exports = service;
function getAll() {
var deferred = Q.defer();
db.redirects.find().toArray(function (err, redirects) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve(redirects);
});
return deferred.promise;
}
function getById(_id) {
var deferred = Q.defer();
db.redirects.findById(_id, function (err, redirect) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve(redirect);
});
return deferred.promise;
}
function getByFrom(from) {
var deferred = Q.defer();
db.redirects.findOne({
from: from
}, function (err, redirect) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve(redirect);
});
return deferred.promise;
}
function create(redirectParam) {
var deferred = Q.defer();
// validate
var errors = [];
if (!redirectParam.from) { errors.push('From is required'); }
if (!redirectParam.to) { errors.push('To is required'); }
if (!errors.length) {
// ensure to and from are lowercase
redirectParam.from = redirectParam.from.toLowerCase();
redirectParam.to = redirectParam.to.toLowerCase();
db.redirects.insert(
redirectParam,
function (err, doc) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
} else {
deferred.reject(errors.join('\r\n'));
}
return deferred.promise;
}
function update(_id, redirectParam) {
var deferred = Q.defer();
// validate
var errors = [];
if (!redirectParam.from) { errors.push('From is required'); }
if (!redirectParam.to) { errors.push('To is required'); }
if (!errors.length) {
// ensure to and from are lowercase
redirectParam.from = redirectParam.from.toLowerCase();
redirectParam.to = redirectParam.to.toLowerCase();
// fields to update
var set = _.omit(redirectParam, '_id');
db.redirects.update(
{ _id: mongo.helper.toObjectID(_id) },
{ $set: set },
function (err, doc) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
} else {
deferred.reject(errors.join('\r\n'));
}
return deferred.promise;
}
function _delete(_id) {
var deferred = Q.defer();
db.redirects.remove(
{ _id: mongo.helper.toObjectID(_id) },
function (err) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
return deferred.promise;
}
```
`getAll()` returns a list of all available redirects from the database and stores it in an array.
`getById()` returns the redirect matching the provided id from the database id.
`getByFrom()` returns the redirect that is a result of a specific page which is used as parameter for this function.
The page that results in the redirect is passed at the initialization of the function and the page redirected is returned at the execution of the function.
`create()` will add a new redirect to complete some operation in the application.
The function uses a parameter `redirectParam` which comes in form of an object.
The object has a number of properties including `from` which is the page the redirect originates from and `to` which is the page the redirect will result in.
`update()` will edit the properties relating to each redirect. In the course of executing update the value of the page the redirect is coming from and the page it will result in can be edited and updated in the database.
`_delete()` will remove any existing redirect from the database.
#### User Service
Create a new file in the services directory `user.service.js`. It will handle all database communications for user operations in our application.
The code for the user service
```
var config = require('config.json');
var _ = require('lodash');
var jwt = require('jsonwebtoken');
var bcrypt = require('bcryptjs');
var Q = require('q');
var mongo = require('mongoskin');
var db = mongo.db(config.connectionString, { native_parser: true });
db.bind('users');
var service = {};
service.authenticate = authenticate;
service.getAll = getAll;
service.getById = getById;
service.create = create;
service.update = update;
service.delete = _delete;
module.exports = service;
function authenticate(username, password) {
var deferred = Q.defer();
db.users.findOne({ username: username }, function (err, user) {
if (err) deferred.reject(err.name + ': ' + err.message);
if (user && bcrypt.compareSync(password, user.hash)) {
// authentication successful
deferred.resolve(jwt.sign({ sub: user._id }, config.secret));
} else {
// authentication failed
deferred.resolve();
}
});
return deferred.promise;
}
function getAll() {
var deferred = Q.defer();
db.users.find().toArray(function (err, users) {
if (err) deferred.reject(err.name + ': ' + err.message);
// return users (without hashed passwords)
users = _.map(users, function (user) {
return _.omit(user, 'hash');
});
deferred.resolve(users);
});
return deferred.promise;
}
function getById(_id) {
var deferred = Q.defer();
db.users.findById(_id, function (err, user) {
if (err) deferred.reject(err.name + ': ' + err.message);
if (user) {
// return user (without hashed password)
deferred.resolve(_.omit(user, 'hash'));
} else {
// user not found
deferred.resolve();
}
});
return deferred.promise;
}
function create(userParam) {
var deferred = Q.defer();
// validation
db.users.findOne(
{ username: userParam.username },
function (err, user) {
if (err) deferred.reject(err.name + ': ' + err.message);
if (user) {
// username already exists
deferred.reject('Username "' + userParam.username + '" is already taken');
} else {
createUser();
}
});
function createUser() {
// set user object to userParam without the cleartext password
var user = _.omit(userParam, 'password');
// add hashed password to user object
user.hash = bcrypt.hashSync(userParam.password, 10);
db.users.insert(
user,
function (err, doc) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
}
return deferred.promise;
}
function update(_id, userParam) {
var deferred = Q.defer();
// validation
db.users.findById(_id, function (err, user) {
if (err) deferred.reject(err.name + ': ' + err.message);
if (user.username !== userParam.username) {
// username has changed so check if the new username is already taken
db.users.findOne(
{ username: userParam.username },
function (err, user) {
if (err) deferred.reject(err.name + ': ' + err.message);
if (user) {
// username already exists
deferred.reject('Username "' + req.body.username + '" is already taken')
} else {
updateUser();
}
});
} else {
updateUser();
}
});
function updateUser() {
// fields to update
var set = {
username: userParam.username,
};
// update password if it was entered
if (userParam.password) {
set.hash = bcrypt.hashSync(userParam.password, 10);
}
db.users.update(
{ _id: mongo.helper.toObjectID(_id) },
{ $set: set },
function (err, doc) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
}
return deferred.promise;
}
function _delete(_id) {
var deferred = Q.defer();
db.users.remove(
{ _id: mongo.helper.toObjectID(_id) },
function (err) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
return deferred.promise;
}
```
`authenticate()` will verify the a user in the database by comparing the provided the username and password with the ones in the database to see if there's a match with one of the usernames and password.
`getAll()` finds and returns a list of all available users from the database and stores the list in an array.
`getById()` finds a user using a provided id and returns the user details for the user if his data is found.
`create()` will add a new user to the database using the parameters provided at the initialization of the function.
Each newly created user has a username and during creation the function checks the database to see if that same username has not been used by another user.
If the provided username has not been used before the function then creates a new user by running the `createUser()` function which inserts the new user details to the database.
`update()` uses the id provided to locate the user details which will be edited with new values in the database.
If the username provided matches a username in the database the function proceeds to make the necessary edits.
In the process of editing if the username provided does not match an existing username in the database the function `updateUser()` runs which will update all the values provided to the specified tables in the database.
`_delete()` will remove a specified user field with matching id as the one passed to the function.
### Curriculum
1. [Building A Content Management System Using The MEAN Stack - 1 (Create Server, Config File and Helper Modules)](https://steemit.com/utopian-io/@gotgame/5b98i4-building-a-content-management-system-using-the-mean-stack-1-create-server-config-file-and-helper-modules)
2. [Building A Content Management System Using The MEAN Stack - 2(Create Controller Modules 1)](https://steemit.com/utopian-io/@gotgame/building-a-content-management-system-using-the-mean-stack-1-create-controller-modules-1)
3. [Building A Content Management System Using The MEAN Stack - 3 (Create Controller Modules 2)](https://steemit.com/utopian-io/@gotgame/building-a-content-management-system-using-the-mean-stack-2-create-controller-modules-1)
Proof Of Work Done
[https://github.com/olatundeee/mean-cms](https://github.com/olatundeee/mean-cms)