Building A Content Management System Using The MEAN Stack - 4 (Create Services Modules)

in utopian-io •  2 months ago

Repository

https://github.com/nodejs/node

What Will I Learn

The codebase for this tutorial is based on MEANie an open source content management system by Jason Watmore.

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

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 linemongo.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)

  2. Building A Content Management System Using The MEAN Stack - 2(Create Controller Modules 1)

  3. Building A Content Management System Using The MEAN Stack - 3 (Create Controller Modules 2)

Proof Of Work Done
https://github.com/olatundeee/mean-cms

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Thank you for your contribution.

  • We suggest that you remove the spaces between the lines of code. Your code formatted this way is hard to read and looks very long.

  • Put some pictures of your application. So we can see the progress.

I hope in the next tutorial follow our advice. Good job!

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!

Reply !stop to disable the comment. Thanks!

Hi @gotgame!

Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server

Hey, @gotgame!

Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Congratulations @gotgame! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @gotgame! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Support SteemitBoard's project! Vote for its witness and get one more award!