MOSAICOS
A place for memories

BUILDING A CRM MICRO-SERVICE WITH JWT AUTHENTICATION IN NODEJS

Published: 2017-04-21

Customer Relationship Management (CRM) is a central module in every e-commerce application since it manages users (buyers / sellers), organizations, sessions, as well as authentication.

In this post we will be building a CRM micro-service in NodeJS from scratch and explaining every technical choice on the libraries used.

Project init

Let's start by creating the basic project structure. Before doing so, you should have NodeJS (version > 6.0.0) as well as NPM (version 3.8.6).

Start by initializing the node project:

$ mkdir skeleton-ecrm-node
$ cd skeleton-ecrm-node/
$ npm init

NPM will guide you through the project init, setting the project name, author, version, etc.

Http server with express.js

Now that we have our project ready, let's create the main app.js file.

Boilerplate code aside, this simple script declares an express application along with its routing (profile and session routes).

Profiles CRUD

Now that we have a working http server, let's write our basic profile controller, service and model and routes.

Our project folder should have the following structure:

skeleton-ecrm-node
    ├── app.js
    ├── package.json
    └── profile
        ├── profile-controller.js
        ├── profile-model.js
        ├── profile-routes.js
        └── profile-service.js

The profile controller will contain the functions invoked by each route (in profile-routes.js) and that process the http requests.

Every business logic should be implemented at the services, which are imported by the controller for processing the results from the database and adding them to the http response object.

profile-controller.js

The controller validates incoming requests and delegates to the service layer:

const Profile = require('./profile-model');
const ProfileService = require('./profile-service');

exports.createProfile = function (req, res, next) {
    requestValidation(req);
    const err = req.validationErrors();
    if (err) {
        return next(err);
    } else {
        const profile = new Profile({
            name: req.body.name,
            email: req.body.email,
            password: req.body.password
        });
        ProfileService.create(profile, (err, profile) => {
            if (err) {
                return next(err);
            }
            res.json(profile);
        });
    }
};

profile-model.js

The model uses Mongoose for MongoDB integration and bcrypt for password hashing:

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const Schema = mongoose.Schema;

const SALT_WORK_FACTOR = 10;

const ProfileSchema = Schema({
    name: {type: String, required: true},
    email: {type: String, required: true},
    password: {type: String, select: false}
});

// Pre save - encrypt user password
ProfileSchema.pre('save', function (next) {
    const user = this;
    if (!user.password) return next();
    
    bcrypt.genSalt(SALT_WORK_FACTOR, (err, salt) => {
        if (err) return next(err);
        bcrypt.hash(user.password, salt, (err, hash) => {
            if (err) return next(err);
            user.password = hash;
            next();
        });
    });
});

module.exports = mongoose.model('Profile', ProfileSchema);

profile-routes.js

const express = require('express');
const router = express.Router();
const ProfileController = require('./profile-controller');

router.post('/profiles', ProfileController.createProfile);
router.get('/profiles/:id', ProfileController.getProfile);
router.get('/profiles', ProfileController.getAllProfiles);
router.put('/profiles/:id', ProfileController.updateProfile);
router.delete('/profiles/:id', ProfileController.deleteProfile);

module.exports = router;

profile-service.js

The service layer handles validation and database operations. It validates profile data, checks for duplicates, and performs CRUD operations:

const Profile = require('./profile-model');
const validator = require('validator');

exports.create = function (profile, next) {
    if (!validateProfile(profile)) {
        const err = new Error('Invalid profile format.');
        err.status = 409;
        return next(err)
    }
    // Check for existing email
    Profile.count({email: profile.email}, (err, count) => {
        if (count !== 0) {
            const err = new Error('Email already exists!');
            err.status = 409;
            return next(err);
        }
        profile.save((err, profile) => {
            return next(err, profile);
        });
    });
};

Conclusion

This demonstrates a basic CRM microservice structure using NodeJS, Express, MongoDB, and JWT authentication. The separation of concerns between controller, service, model, and routes follows best practices for maintainable code.