Building REST API with Nodejs / MongoDB /Passport /JWT

Building REST API with Nodejs / MongoDB /Passport /JWT

In this lesson, we will start our journey on making REST APIs with NodeJSalong with MongoDBdatabase. if you don’t have experience on Nodejs and MongoDB before

In this lesson, we will start our journey on making REST APIs with NodeJSalong with MongoDBdatabase. if you don’t have experience on Nodejs and MongoDB before

Why this tutorial?

When I started learning to program, I was searching for a solution to my problems and found it. But the problem was I didn’t know why something was working when it was and why it wasn’t. I had to copy other’s source code and use it without knowing why was something done.

This tutorial will help you to go through all the boilerplate code you can use and still understand each part of it.

What will we be making?

We will be making a website very similar to the Medium website with REST standards. We will also use the following functionalities:

  • Authentication local + JWT
  • User can create a post
  • User can delete his own post and update it
  • User can follow another user’s post
  • User gets a notification for a post made by a user he is following
  • User can like a post
  • User can see a list of all the positions he has liked

Sounds interesting, right? Let’s see what we will be using to make this wonderful app.

Tech stack for the app

We will be using Javascript, ES6 + ES7 and we will compile our source using babel and Webpack v2. You should be familiar with JS promises and async working as well.

For the database, we will be using MongoDB.

All code in Github HERE

Setting up the tools

To start with Part 1 of this series, we will be setting up our environment with the following tools:

  • Editorconfig
  • Express
  • Eslint
  • Babel
  • Webpack 2

Once we finish this post, we will be having a simple Express server up and running. Let’s get started!

Just make a new directory for your project. I called it ‘makenodejsrestapi’. I will be using yarnpackage to install my tools. Inside the directory, we first create a new file called .gitignore and add the following contents:

node_modules/

Now, we will be initializing our project by running the following command:

yarn init

You will be asked various questions, for which I just hit Enter key and let yarntake default values. Once the command has completed its work, you will see a new file in the project directory named as _package.json_with following content:

{ 
 “name”: “makenodejsrestapi”, 
 “version”: “1.0.0”, 
 “main”: “index.js”, 
 “license”: “MIT” 
}

This file just contains the meta-data for our project. Next, we will start adding the express in our project. Run the following command next:

yarn add express

If this package is not found initially, the yarn will take some time to find it but it surely will. Once the command is done running, our package.json will be updated with the following content:

Next, we create a new directory in our project named as src and create a new file named as an index.js file in it. Put the following content in it:

import express from 'express';
 const app = express();
 const PORT = process.env.PORT || 3000;
 app.listen(PORT, err => {
     if (err) {
         throw err;
     } else {
         console.log(Server running on port: $ {
             PORT
         }-- - Running on $ {
             process.env.NODE\_ENV
         }-- - Make something great!)
     }
 });

Note that we are using a port 3000 if a port is not set in the environment variables. Now, we will add a ‘script’ in our package.json file so that we can use dev profile while running our using babel. Here is the modified file:

Now, install the cross-env with yarn with this command:

yarn add cross-env

This is the updated package.json file:

{
     "name": "makenodejsrestapi",
     "version": "1.0.0",
     "main": "index.js",
     "license": "MIT",
     "scripts": {
         "dev": "NODE\_ENV=development node src/index.js"
     },
     "dependencies": {
         "cross-env": "^5.1.3",
         "express": "^4.16.2"
     }
 }

Now we can add babel dependencies with this command:

yarn add -D babel-preset-env babel-plugin-transform-object-rest-spread

Once you run the command, you can create a file called .babelrc in which you can provide environment and plugin information about the app. This is what we will do next:

{
     "presets": [
         ["env", {
             "targets": {
                 "node": "current"
             }
         }]
     ],
     "plugins": [
         ["transform-object-rest-spread", {
             "useBuiltIns": true
         }]
     ]
 }

The transform-object-rest-spread plugin is used to transform rest properties for object destructuring assignments. Now, we will use webpack 2 as well:

yarn add -D webpack babel-core babel-loader webpack-node-externals

Finally, we will configure the webpack as well as we have added its dependencies above as well:

const nodeExternals = require('webpack-node-externals');
 const path = require('path');
 module.exports = {
     target: 'node',
     externals: [nodeExternals()],
     entry: {
         'index': './src/index.js'
     },
     output: {
         path: path.join(\_\_dirname, 'dist'),
         filename: '[name].bundle.js',
         libraryTarget: 'commonjs2',
     },
     module: {
         rules: [{
             test: /\.js$/,
             exclude: /node\_modules/,
             use: 'babel-loader'
         }]
     }
 }

Now, we run our package.json script as well:

"scripts": { "dev:build": "webpack -w", "dev": "cross-env NODE\_ENV=development node dist/index.bundle.js" }

Finally, we can run our app:

To look at it graphically, here is the output when we run the build:

Note that we ran two commands above:

  • The first command just built the application and prepared babel build
  • The second command actually runs the request and you can see the output in the console

Now, we will finally install ES Lint as well:

yarn add -D eslint eslint-config-equimper

Now, make a new file called ‘.eslintrc’ and add the following content:

{ “extends” : “equimper” }

Once you do this, you will start to get warnings if you are not following correct ES standards. This tool is very helpful when you need to follow strict conventions for your project.

A premium training course to learn to build apps with Node.js, Express, MongoDB, and friends. Start Learning Now →

Next What we will be adding now?

In this section, we will be setting up more tools needed for the backend of this application:

  • Add mongoose, body-parser, morgan, compression, helmet
  • Setup config folder
  • Setup constants

Adding Mongoose

To add mongoose and other mentioned modules to your application, run the following command:

yarn add mongoose body-parser compression helmet && yarn add -D morgan

This is to be noted that the order in which we specify the modules, they will be downloaded in the same order.

Just to make sure we are on the same track, here is how my package.json file looks like:

Now, we will compile our project again with this command:

yarn dev

Just make sure that the project is still running. Now, make a new config folder inside the src folder and make a file named as constants.js with following contents:

const devConfig = {};
const testConfig = {};
const prodConfig = {};
const defaultConfig = {

PORT: process.env.PORT || 3000,
};

function envConfig(env) {
     switch (env) {
         case 'development':
             return devConfig;
         case 'test':
             return testConfig;
         default:
             return prodConfig;
     }
 }

 //Take defaultConfig and make it a single object 
 //So, we have concatenated two objects into one 
 export default { ...defaultConfig,
     ...envConfig(process.env.NODE\_ENV),
 };

Now, moving back to the index.js file, we will add a dependency for this constants file and change references to PORT to use this file like:

import express from 'express';
import constants from './config/constants';
const app = express();
app.listen(constants.PORT, err => {
    if (err) {
        throw err;
    } else {
        console.log(`Server running on port: ${constants.PORT} --- Running on ${process.env.NODE_ENV} --- Make something great.!`)
    }
});

Now, make a new file named as database.js in config folder with the following contents:

import mongoose from 'mongoose';
 import constants from './constants';

 //Removes the warning with promises 
 mongoose.Promise = global.Promise;

 //Connect the db with the url provided 
 try {
     mongoose.connect(constants.MONGO\_URL)
 } catch (err) {
     mongoose.createConnection(constants.MONGO\_URL)
 }
 mongoose.connection.once('open', () => console.log('MongoDB Running')).on('error', e => {
     throw e;
 })

We also modified the config for mongoose connection in our constants.js file as:

const devConfig = { MONGO\_URL: 'mongodb://localhost/makeanodejsapi-dev', }; 
 const testConfig = { MONGO\_URL: 'mongodb://localhost/makeanodejsapi-test', }; 
 const prodConfig = { MONGO\_URL: 'mongodb://localhost/makeanodejsapi-prod', };

This will make sure that the database used is different when we run our application with different profiles and environments. You can go on and run this application again.

When you have a database running on the said port, you can successfully start using your application.

Designing the Middleware

Now, we will start making the middleware of the application.

In the config folder, make a new file and name it as middleware.js with the following content:

import morgan from 'morgan';
 import bodyParser from 'body-parser';
 import compression from 'compression';
 import helmet from 'helmet';
 import {
     isPrimitive
 } from 'util';
 const isDev = process.env.NODE\_ENV === 'development';
 const isProd = process.env.NODE\_ENV === 'production';
 export default app => {
     if (isProd) {
         app.use(compression());
         app.use(helmet());
     }
     app.use(bodyParser.json());
     app.use(bodyParser.urlencoded({
         extended: true
     }));
     if (isDev) {
         app.use(morgan('dev'));
     }
 };

To put this config to use, add the import to index file as well, like:

import express from 'express';
 import constants from './config/constants';
 import './config/database';
 import middlewareConfig from './config/middleware';
 const app = express(); //passing the app instance to middlewareConfig 

 middlewareConfig(app);
 app.listen(constants.PORT, err => {
     if (err) {
         throw err;
     } else {
         console.log(`Server running on port: ${constants.PORT} --- Running on ${process.env.NODE_ENV} --- Make something great.!`)
     }
 });

Now, run your application and it should be serving a GET request on Port 3000!

Register User

In this section, we will use the MongoDB setup we did in the last lesson and build up an app from there allowing a user to register in our app. Just that you do not miss out, our latest package.json file looks like this:

In this section, we will step ahead by making functionality for allowing a user to register on our application. We will also be making the User model so that the data can be saved in the database.

Once you are done with this section, you will have at least following file structure:

Just follow along with the lesson how this turns out!

Defining the model

We will start by making our User model. To do this, create a new file at src > modules > users and name it as user.model.js with the following content:

import mongoose, {
     Schema
 } from 'mongoose';
 import validator from 'validator';
 import {
     passwordReg
 } from './user.validations';
 const UserSchema = new Schema({
     email: {
         type: String,
         unique: true,
         required: [true, 'Email is required!'],
         trim: true,
         validate: {
             validator(email) {
                 return validator.isEmail(email);
             },
             message: '{VALUE} is not a valid email!',
         },
     },
     firstName: {
         type: String,
         required: [true, 'FirstName is required!'],
         trim: true,
     },
     lastName: {
         type: String,
         required: [true, 'LastName is required!'],
         trim: true,
     },
     userName: {
         type: String,
         required: [true, 'UserName is required!'],
         trim: true,
         unique: true,
     },
     password: {
         type: String,
         required: [true, 'Password is required!'],
         trim: true,
         minlength: [6, 'Password need to be longer!'],
         validate: {
             validator(password) {
                 return passwordReg.test(password);
             },
             message: '{VALUE} is not a valid password!',
         },
     },
 });
 export default mongoose.model('User', UserSchema);

We just defined the schema of our User model with various properties like:

  • Defined properties for a user
  • Also provided the meta-information about the properties of their types, their uniqueness and how this data should be validated
  • Note how we provided a validation function as well. This makes things very easy when we are inserting the data into the Mongo collections

Defining the Controller

Now, we will put the User model to use by using it in the Controller definition. Create a new file at src > modules > users and name it as user.controllers.js with the following content:

import User from './user.model';
 export async function signUp(req, res) {
     try {
         const user = await User.create(req.body);
         return res.status(201).json(user);
     } catch (e) {
         return res.status(500).json(e);
     }
 }

We just defined a signUp function with a request and response object as parameters and created it using the User model we just defined above.

We also returned an appropriate response along with their codes so that user can be notified if the transaction was successful.

Defining application routes

We will define for routes for our application where we can specify the mapping where a User must visit to see the application we made. Create a new file at src > modules > users and name it as user.routes.js with the following content:

import {
     Router
 } from 'express';
 import \* as userController from './user.controllers';
 const routes = new Router();
 routes.post('/signup', userController.signUp);
 export default routes;

Note that this will not work. We must define module index.js inside the modules folder with the following content:

import userRoutes from './users/user.routes';
 export default app => {
     app.use('/api/v1/users', userRoutes);
 };

Now we can run our application which will be the actual first version of our app. Just that we need to make final changes to our root index.js file now:

Here is the updated content:

/\* eslint-disable no-console \*/
 import express from 'express';
 import constants from './config/constants';
 import './config/database';
 import middlewaresConfig from './config/middlewares';
 import apiRoutes from './modules';
 const app = express();
 middlewaresConfig(app);
 app.get('/', (req, res) => {
     res.send('Hello world!');
 });
 apiRoutes(app);
 app.listen(constants.PORT, err => {
     if (err) {
         throw err;
     } else {
         console.log(` Server running on port: ${constants.PORT} --- Running on ${process.env.NODE_ENV} --- Make something great `);
     }
 });

Now when we run the app, we can still see that our app is running:

Postman is useful for API Testing to take this course to help your understand how they're useful

A premium training course to learn to build apps with Node.js, Express, MongoDB, and friends. Start Learning Now →

Using MongoDB and Postman

We will now be using two tools which are necessary:

  1. Robomongo: Download it here. It is an awesome tool to visualize Mongo data and query with it. It’s free as well! This is available for all OS platforms.
  2. Postman: Download it here. It is a tool to hit APIs and get a response back. It has great visualization features and you can save request format too which saves you a lot of time. Again, it’s free! This is available for all OS platforms.

When you open Robomongo and connect to your local MongoDB instance, you can see the DB present for our already:

We already have a collection ready which was made by our app, it will look like:

This will be empty right now as we haven’t created any data as of now. We will be doing this really soon!

Trying the User Registration

Let’s open postman now. We will hit an API with this URL:

http://localhost:3000/api/v1/users/signup

In postman, it will look something like:

Before hitting this API, we will try a Hello World version. See what happens in this API:

Now, back to the registration API. Before doing a successful hit, we will try to provide invalid values and see what errors we face. In case of an invalid email, here is the result:

Now, we will try with correct data as well. Let’s try it!

Well, the story isn’t over yet. We can also see the data being inserted into the MongoDB database now:

Excellent!

Adding more validations

Though we already added some validations in the User model, what if we want to keep in another file too! To do this, create a new file at src > modules > users and name it as user.validations.js with the following content:

import Joi from 'joi';
 export const passwordReg = /(?=.\*\d)(?=.\*[a-z])(?=.\*[A-Z]).{6,}/;
 export default {
     signup: {
         email: Joi.string().email().required(),
         password: Joi.string().regex(passwordReg).required(),
         firstName: Joi.string().required(),
         lastName: Joi.string().required(),
         userName: Joi.string().required(),
     },
 };

Next, add this validation in the router file as well:

import {
       Router
   } from 'express';
   import validate from 'express-validation';
   import \* as userController from './user.controllers';
   import userValidation from './user.validations';
   const routes = new Router();
   routes.post('/signup', validate(userValidation.signup), userController.signUp);
   export default routes;

Notice that:

  • We added an import for validations from express
  • We also passed a new function for user validation for sign up

Now when we try a user to register with the same credentials, we get an error:

This was not actually because of the validation but because we are trying to insert another user with the same email. Let’s try something different:

Now, we can correct this and see the data appearing in MongoDB database:

Excellent! We were able to add powerful validations to our project.

Password Encryption and User Login

We will be working more with our User. In our last lesson, we were able to save a new User. The main issue with this approach was that the password of the User was saved as plain text. This will be one of the changes we will be making in our app now.

Just that you do not miss out, our latest package.jsonfile looks like this:

In this lesson, we will step ahead by making functionality for encrypting a User’s password. Apart from this, we will also be making changes like:

  • Add rimraf and clean dist on webpack build
  • Encrypt the user’s password
  • Create the local strategy with passport
  • Allow user login

Adding rimraf dependency

We will start by adding rimraf dependency in our project by using the following command:

yarn add -D rimraf

Just to build your project again, run the following command:

yarn

Now, let’s add rimraf to our package.json file as well:

“scripts”: {
 “clean”: “rimraf dist”,
 “dev:build”: “yarn run clean && webpack -w”,
 “dev”: “cross-env NODE\_ENV=development nodemon dist/index.bundle.js”
}

Now, run the following command:

yarn dev:build

When you run this command, the dist folder will be refreshed and come back once the build process is complete:

Library to encrypt a password

Now, we will be adding a library to our project so that we can encrypt a user’s password before it is saved to the database. This way, we can ensure that it is safe even if a database is hacked.

Run the following command:

yarn add bcrypt-nodejs

This way, the library will be added to our project.

Modifying the model

Now, we will have to modify our model so that an encrypted password can be set in it when a plain-text password comes in the request. Add the following in user.model.js

UserSchema.pre('save', function(next) {
     if (this.isModified('password')) {
         this.password = this.\_hashPassword(this.password);
     }
     return next();
 });
 UserSchema.methods = {
     \_hashPassword(password) {
         return hashSync(password);
     },
     authenticateUser(password) {
         return compareSync(password, this.password);
     },
 };

In the above code, this refers to our current user mentioned by the request. Also, the authenticateUser is called as soon as we try logging in and a password which is a plain-text is passed by the user. we hash this password and only after that we will compare it to the value in our Database.

Now, let’s try to make a new request and check if this works. Here is my request:

When I run this request, this is the response we get back:

Let’s check out the database now, we will see a similar scene there too:

Now, we will be a login API for our application.

Using Passport for Login

We will be using a library known as Passport. You are free to use any other authentication library like Facebook, Google etc.

To continue we need to add two libraries to our project. Lets’ do this by running the following command:

yarn add passport passport-local

Once this is done, let’s create a new folder inside the src folder known as services. We will be creating a new file named as auth.services.js inside the services folder with the following content:

import passport from 'passport';
 import LocalStrategy from 'passport-local';
 import User from '../modules/users/user.model';
 const localOpts = {
     usernameField: 'email',
 };
 const localStrategy = new LocalStrategy(localOpts, async (email, password, done) => {
     try {
         const user = await User.findOne({
             email
         });
         if (!user) {
             return done(null, false);
         } else if (!user.authenticateUser(password)) {
             return done(null, false);
         }
         return done(null, user);
     } catch (e) {
         return done(e, false);
     }
 });
 passport.use(localStrategy);
 export const authLocal = passport.authenticate('local', {
     session: false
 });

Here, we tried a local strategy which will be async in nature and data is sent to the passport library as our user’s email and password. The library will then authenticate the user and will return the response.

We will add Passport as our middleware as well. Here is the modified file:

import morgan from 'morgan';
 import bodyParser from 'body-parser';
 import compression from 'compression';
 import helmet from 'helmet';
 import passport from 'passport';

 const isDev = process.env.NODE\_ENV === 'development';
 const isProd = process.env.NODE\_ENV === 'production';

 export default app => {
     if (isProd) {
         app.use(compression());
         app.use(helmet());
     }
     app.use(bodyParser.json());

     app.use(bodyParser.urlencoded({
         extended: true
     }));
     app.use(passport.initialize());

     if (isDev) {
         app.use(morgan('dev'));
     }
 };

Here, we initialized the Passport library with our app instance as well.

Adding Login to Controller

It is time that we add the login functionality to our Controller layer as well. Add the following function to our Controller:

export function login(req, res, next) {
 res.status(200).json(req.user);
 return next();
}

Note that this is how our final Controller file looks like:

Providing Route for login

We will have to provide a route to login API as well. We will be making this change in user.routes.jsfile. Add this route to the file along with the import:

import {
 authLocal
} from ‘../../services/auth.services’;

routes.post(‘/login’, authLocal, userController.login);

Here is how our final file looks like:

Trying the Login functionality

We will try the following POST API now with the credentials we previously created:

http://localhost:3000/api/v1/users/login

When the credentials are correct, this is what happens:

Isn’t that excellent? We were able to login to an existing user, and we were also able to protect his password by encrypting it.

Adding JWT Authentication

Till now, we are able to register a new user in our application:

We are also able to allow a user to login into our application:

Before knowing what we will be making in this post, let’s finally see how our current _package.json_file looks:

In this section, we will be adding the following functionalities:

  • We will implement JWT authentication and add a secret password
  • Add the new passport-jwt library
  • Add JSON web token library
  • Sending only required fields as a response in JSON

How does JSON web token stores data?

When we provide data to encrypt along with a secret password, it is encrypted to form various parts of a JWT token, such as:

As shown above, a single token can contain a users identity and other data associated with him.

Adding JWT secret

Let’s move on to our _constants.js_file and add a JWT secret here in dev config present already:

const devConfig = {
 MONGO\_URL: ‘mongodb://localhost/makeanodejsapi-dev’,
 JWT\_SECRET: ‘thisisasecret’,
};

Next, we will be installing two libraries by using the following command:

yarn add jsonwebtoken passport-jwt

Now, move to the auth services file and JWT service in the file with this line:

import { Strategy as JWTStrategy, ExtractJwt } from ‘passport-jwt’;

import User from ‘../modules/users/user.model’;
import constants from ‘../config/constants’;

Next, let passport use the specified strategy:

// Jwt strategy
 const jwtOpts = {
   jwtFromRequest: ExtractJwt.fromAuthHeader('authorization'),
   secretOrKey: constants.JWT\_SECRET,
 };

 const jwtStrategy = new JWTStrategy(jwtOpts, async (payload, done) => {
   try {
     //Identify user by ID
     const user = await User.findById(payload.\_id);

     if (!user) {
       return done(null, false);
     }
     return done(null, user);
   } catch (e) {
     return done(e, false);
   }
 });

 passport.use(localStrategy);
 passport.use(jwtStrategy);

 export const authLocal = passport.authenticate('local', { session: false });
 export const authJwt = passport.authenticate('jwt', { session: false });

To test if this works, we will now use a private route in our route JS file. The final file content will look like:

import userRoutes from ‘./users/user.routes’;
import { authJwt } from ‘../services/auth.services’;

export default app => {
 app.use(‘/api/v1/users’, userRoutes);
 app.get(‘/hello’, authJwt, (req, res) => {
 res.send(‘This is a private route!!!!’);
 });
};

Verifying JWT

Let’s try this and verify that JWT is working in Postman now:

We need to add a JWT token in a request now which belongs to a particular user only.

We will add functionality to the User model so that it contains the JWT token as well when a user logs in. So, let’s add more libraries to User model JS file:

import jwt from ‘jsonwebtoken’;
import constants from ‘../../config/constants’;

Now, we can decrypt a token and get user information.

Creating a JWT Token

We will also have to create a method which creates a token for the user. Let’s add this method now:

UserSchema.methods = {

     createToken() {
       return jwt.sign(
         {
           \_id: this.\_id,
         },
         constants.JWT\_SECRET,
       );
     },
     toJSON() {
       return {
         \_id: this.\_id,
         userName: this.userName,
         token: `JWT ${this.createToken()}`,
       };
     },
   };

Using the toJSON() method is important as well. We appended JWT in front of a token as a passport library uses it to identify the JWT token.

Now, let’s try logging a user again:

This time, we even got a JWT token in response. This token will contain the user ID and username as well. We have a working example for JWT now!

Let’s copy the JWT value and try the private route now:

Making a Post by User and Object Association

next, we are able to register a new user in our application:

We are also able to allow a user to login into our application:

Before knowing what we will be making in this post, let’s finally see how our current package.json file looks:

In this section, we will be adding the following functionalities:

  • We will create a new resource for a Post. Now, a User can create a Post as well
  • Make the User as the Post’s author
  • Work on some issues which we created in past posts

Creating Model for Post

Just what we did for the User model, it needs to be done for Post model as well like creating a new folder. By the end of this lesson, you will following the new folder and files in your project:

We will start by creating the Post model. We will also include the validations we need. Let’s add another library for mongoose unique validation:

yarn add mongoose-unique-validator

We will also add a new Slug library. To do this, install it using the following command:

yarn add slug

If you’re wondering what is slugify, a post’s URL should look like the title of the post. This looks good and the glimpse of the post is also visible in its URL which is a good procedure.

Now, we can add this library as well. Our model will look like the following:

import mongoose, { Schema } from 'mongoose';
   import slug from 'slug';
   import uniqueValidator from 'mongoose-unique-validator';

   const PostSchema = new Schema({
     title: {
       type: String,
       trim: true,
       required: [true, 'Title is required!'],
       minlength: [3, 'Title need to be longer!'],
       unique: true,
     },
     text: {
       type: String,
       trim: true,
       required: [true, 'Text is required!'],
       minlength: [10, 'Text need to be longer!'],
     },
     slug: {
       type: String,
       trim: true,
       lowercase: true,
     },
     user: {
       type: Schema.Types.ObjectId,
       ref: 'User',
     },
     favoriteCount: {
       type: Number,
       default: 0,
     },
   }, { timestamps: true });

   PostSchema.plugin(uniqueValidator, {
     message: '{VALUE} already taken!',
   });

   PostSchema.pre('validate', function (next) {
     this.\_slugify();

     next();
   });

   PostSchema.methods = {
     \_slugify() {
       this.slug = slug(this.title);
     },
   };

   PostSchema.statics = {
     createPost(args, user) {
       return this.create({
         ...args,
         user,
       });
     },
   };

   export default mongoose.model('Post', PostSchema);

We did the following in the above model:

  • Defined fields for the Post model
  • Added validation against each field
  • Added validation for overall Post object
  • We slugify the post by its title and we save that value as well

In the code shown above, we will be adding the createPost method next in our controller.

Creating Post Controller

We will now be needing a Controller so that a user should actually be able to do the operations related to a Post.

Based on the directory structure shown above, define a new file post.controller.js file in post module itself with the following content:

import Post from './post.model';

 export async function createPost(req, res) {
   try {
     const post = await Post.createPost(req.body, req.user.\_id);
     return res.status(201).json(post);
   } catch (e) {
     return res.status(400).json(e);
   }
 }

We return an appropriate response when we face an error or we were successfully able to create a new post.

Creating Post Route

Let’s create the route to the Post Controller in our app now in the file named post.route.js file under posts module with the following content:

import { Router } from 'express';

 import \* as postController from './post.controllers';
 import { authJwt } from '../../services/auth.services';

 const routes = new Router();

 routes.post(
   '/',
   authJwt,
 );

 export default routes;

Let’s modify the index.jsfile for this as well. The final content will be:

import userRoutes from ‘./users/user.routes’;
import postRoutes from ‘./posts/post.routes’;

export default app => {
 app.use(‘/api/v1/users’, userRoutes);
 app.use(‘/api/v1/posts’, postRoutes);
};

Verifying the posts API

We will now try the POST API to create a new post.

To start, try login a user so that you get a JWT token to hit the Create Post API on this URL:

http://localhost:3000/api/v1/posts

Here is what we tried and the response:

We have populated date and slug fields as well. This also contains the user ID. Let’s see this post in MongoDB as well:

If we again hit this API to create the post, it will fail as a title is already taken:

This means that our validation is working fine as well.

Making the title as compulsory

We can implement more validations like making the title for a post as compulsory.

Let’s create a new file named as post.validations.js in posts module with the following content:

import Joi from 'joi';

   export const passwordReg = /(?=.\*\d)(?=.\*[a-z])(?=.\*[A-Z]).{6,}/;
   export default {
     signup: {
       body: {
         email: Joi.string().email().required(),
         password: Joi.string().regex(passwordReg).required(),
         firstName: Joi.string().required(),
         lastName: Joi.string().required(),
         userName: Joi.string().required(),
       },
     },
   };

We will have to modify the routes file as well to include this validation. Here is the modified file:

import { Router } from 'express';
 import validate from 'express-validation';
 import \* as postController from './post.controllers';
 import { authJwt } from '../../services/auth.services';
 import postValidation from './post.validations';

 const routes = new Router();
 routes.post(
   '/',
   authJwt,
   validate(postValidation.createPost),
   postController.createPost,
 );

 export default routes;

We were able to get the user ID from the authJwtobject we use above. The message we now receive is:

We will be changing the response soon to be more graceful.

Getting Data by ID and populating an object in another

next, we are able to register a new user in our application:

We are also able to allow a user to login into our application:

We were also able to create a post related to a user as well:

In this section, we will be adding the following functionalities:

  • We will get a post by its ID
  • We will also create Controllers and route
  • We will show you how to populate the user information in a post
  • Other libraries we will use

A premium training course to learn to build apps with Node.js, Express, MongoDB, and friends. Start Learning Now →

Adding HTTP status library to Controller

To add this library, run the following command:

yarn add http-status

Now, we can use this library in our User Controller as well. Let’s start by importing this library:

import HTTPStatus from ‘http-status’;

Next, instead of using status like 200 etc. in our Controller, we will modify status provided by this library as:

export async function signUp(req, res) {
     try {
       const user = await User.create(req.body);
       return res.status(HTTPStatus.CREATED).json(user.toAuthJSON());
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

   export function login(req, res, next) {
       res.status(HTTPStatus.OK).json(req.user.toAuthJSON());
     return next();
   }

We will do the same in the Post Controller as well:

import HTTPStatus from 'http-status';
   import Post from './post.model';

   export async function createPost(req, res) {
     try {
       const post = await Post.createPost(req.body, req.user.\_id);
         return res.status(HTTPStatus.CREATED).json(post);
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

Getting Post by ID

We will define a new function in the Post Controller for Getting a Post by ID:

export async function getPostById(req, res) {
   try {
     const post = await Post.findById(req.params.id);
     return res.status(HTTPStatus.OK).json(post);
   } catch (e) {
     return res.status(HTTPStatus.BAD\_REQUEST).json(e);
   }
 }

Let’s move to define the route for this function:

routes.get(‘/:id’, postController.getPostById);

As we have the following Post in our Mongo Database:

We will get this post via our API:

The problem with this response is that we got back all the fields which were present in MongoDB as well. We do not want this. Let’s change this in the Post model:

PostSchema.methods = {
   \_slugify() {
     this.slug = slug(this.title);
   },
   toJSON() {
     return {
       \_id: this.\_id,
       title: this.title,
       text: this.text,
       createdAt: this.createdAt,
       slug: this.slug,
       user: this.user,
       favoriteCount: this.favoriteCount,
     };
   },
 };

Once we have applied the toJSON()function in our model, this is the response we get back now:

Getting User data in Post response

If we see the above JSONclosely, we actually have the user field which holds its ID. But what if we also want its info in the same object?

Just modify the getPostById function slightly and modify this post const in the function as:

const post = await Post.findById(req.params.id).populate(‘user’);

We just added a populate call and the response will now be:

toJSONwill also work when we populate the user object. This is an issue here as we also got back the token field above which should never happen!

Let’s modify the user model to improve this:

UserSchema.methods = {
   \_hashPassword(password) {
     ...
   },
   authenticateUser(password) {
     ...
   },
   createToken() {
     ...
   },
   toAuthJSON() {
     ...
   },
   toJSON() {
     return {
       \_id: this.\_id,
       userName: this.userName,
     };
   },

We modified the toJSONmethod above so token field is not included in the response itself.

The issue is still present actually. Let’s see what happens when I try logging a user in:

See, no token field is present here as well. To resolve this, go to the login function in User controllerand modify as follows:

export function login(req, res, next) {
 res.status(HTTPStatus.OK).json(req.user.toAuthJSON());
 return next();
}

Now, I have used the toAuthJSONfunction itself. If you try now, login will work like before!

Getting All data from Database

next, we are able to register a new user in our application:

We are also able to allow a user to login into our application:

We were also able to create a post related to a user as well:

In this section, we will be adding the following functionalities:

  • Work on Post Controller to add more functionality

Extending Controller

Till now, we only have the following functionality in our Post Controller:

  • Create a post
  • Get Post By ID

Now, we will also add more functionality and we will start by getting all the posts in a list.

Getting all posts

Let’s extend the functionality in our Post Controller by adding a new method to get all posts:

export async function getPostsList(req, res) {
     try {
       const posts = await Post.find().populate('user');
       return res.status(HTTPStatus.OK).json(posts);
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

Here, we returned the posts. Let’s modify the route file to use this function we added above:

routes.get(‘/’, postController.getPostsList);

We haven’t added this in authentication in this to enable even an unauthenticated user to allow him to at least the posts. Let’s try this API now:

Right now, we have 11 posts in Database and so, above API shows no problem. But what happens when there are more than 50,000 posts? We wil have serious performance issues in such cases.

Pagination to the rescue

We can return a limited number of posts based on user request. In postmodel, we can provide pagination parameters like:

PostSchema.statics = {
     createPost(args, user) {
       ...
     },
     list({ skip = 0, limit = 5 } = {}) {
       return this.find()
         .sort({ createdAt: -1 })
         .skip(skip)
         .limit(limit)
         .populate('user');
     },
   };

What list does is, it returns only 1st 5 posts initially. If skip is 5, list function will return 5 posts but after it has skipped 1st 5 posts. Let’s modify the Controller as well:

export async function getPostsList(req, res) {
   const limit = parseInt(req.query.limit, 0);
   const skip = parseInt(req.query.skip, 0);
   try {
     const posts = await Post.list({ limit, skip });
     return res.status(HTTPStatus.OK).json(posts);
   } catch (e) {
     return res.status(HTTPStatus.BAD\_REQUEST).json(e);
   }
 }

Now when we provide these values, we get this response:

Updating a Post and Adding Validations

next, we are able to register a new user in our application:

We are also able to allow a user to login into our application:

We were also able to create a post related to a user as well:

In this lesson, we will be adding the following functionalities:

  • We will update a post and make sure that the user who is updating the post is its author
  • Create a validation field

We will add more operations on a post in the coming lessons.

A premium training course to learn to build apps with Node.js, Express, MongoDB, and friends. Start Learning Now →

Extending Controller

Till now, we only have the following functionality in our Post Controller:

  • Create a pos
  • Get Post By ID
  • Get a list of all Posts

Now, we will also add more functionality and we will start by allowing a user to update a post.

Updating a Post

Let’s extend the functionality in our Post Controller by adding a new method to update a post:

export async function updatePost(req, res) {
     try {
       const post = await Post.findById(req.params.id);
       if (!post.user.equals(req.user.\_id)) {
         return res.sendStatus(HTTPStatus.UNAUTHORIZED);
       }

       Object.keys(req.body).forEach(key => {
         post[key] = req.body[key];
       });

       return res.status(HTTPStatus.OK).json(await post.save());
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

This is what we did above:

  • Confirm from JWT token if the user is same as user present in Post object
  • We return an UNAUTHORIZEDresponse if a user is not the same
  • If a user is same, we get each key passed in a request and update the post based on that
  • Once all updates are done, we return the OK response

Let’s modify the validations file to use this function we added above:

import Joi from 'joi';

   export default {
     createPost: {
       body: {
         title: Joi.string().min(3).required(),
         text: Joi.string().min(10).required(),
       },
     },
     updatePost: {
       body: {
         title: Joi.string().min(3),
         text: Joi.string().min(10),
       },
     },
   };

We just added validations in updatePost function for a minimum length of two fields. Time for route file now:

routes.patch(
   '/:id',
   authJwt,
   validate(postValidation.updatePost),
   postController.updatePost,
 );

Updating a Post

Now that the work is done now, we will verify the work we did above. Let’s make a PATCHrequest from Postman like this:

Excellent, it worked! Even the slug for the post was updated. Just make sure we have this method in a model for Post:

PostSchema.pre(‘validate’, function (next) {
 this.\_slugify();
 next();
});

Go on and try the same with Post text as well.

Deleting a Post by Authorized User

Till now, we are able to register a new user in our application:

We are also able to allow a user to login into our application:

We were able to create a post related to a user:

In this lesson, we will be adding the following functionalities:

  • We will allow an author to delete a post
  • Authorization functionality
  • Add a tool called prettie

Extending Controller

Till now, we only have the following functionality in our Post Controller:

  • Create a post
  • Get Post By ID
  • Get list of all Posts
  • Update Posts

Now, we will also add more functionality and we will start by allowing a user to delete a post.

Deleting a Post

Let’s extend the functionality in our Post Controller by adding a new method to delete a post:

export async function deletePost(req, res) {
     try {
         const post = await Post.findById(req.params.id);

       if (!post.user.equals(req.user.\_id)) {
         return res.sendStatus(HTTPStatus.UNAUTHORIZED);
       }

       await post.remove();
       return res.sendStatus(HTTPStatus.OK);
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

This is what we did above:

  • Confirm from JWT token if the user is same as user present in Post object
  • We return an UNAUTHORIZED response if the user is not the same
  • If the user is same, we remove the post
  • Once Post is deleted, we return the OK response

Time for route file now:

routes.delete(‘/:id’, authJwt, postController.deletePost);

Deleting a Post

Now that the work is done now, we will verify the work we did above. Let’s make a DELETErequest from Postman like this:

You can now verify that this Post is not present in the Get all Post API and the MongoDB as well with a query like:

Adding prettier library

We can add prettier library with the following yarn command:

yarn add -D prettier

Once this is done, here is my updated package.jsonfile:

{
     "name": "makeanodejsrestapi",
     ...,
     "scripts": {
       ...,
       "prettier": "prettier --single-quote --print-width 80 --trailing-comma all --write 'src/\*\*/\*.js'"
     },
     "dependencies": {
       ...
     },
     "devDependencies": {
       ...,
       "prettier": "^1.3.1",
       ...
     }
   }

We only displayed what changes were done. We will also add an ES lint library with the following command:

yarn add -D eslint-config-prettie

Now, we will make a new file named as .eslintrc with the following comment:

{
 “extends”: [
 “equimper”,
 “prettier”
 ]
}

Now, if you forget to add semi-colons or indentations, you just need to run the following command and they will be added for you:

yarn prettier

Isn’t that magic? :) This also shows what files were changed:

We will continue using this command and library as this really eases our work!

A premium training course to learn to build apps with Node.js, Express, MongoDB, and friends. Start Learning Now →

Favorite a Post and managing Statistics of a Post

next, we are able to register a new user in our application:

We are also able to allow a user to login into our application:

We were able to create a post related to a user:

In this section, we will be adding the following functionalities:

  • The user can favorite a post when authenticated which will also increment the favoriteCount counter variable
  • Modify models for both User and Post for this
  • Add increment/decrement statics on Post

Modifying the User model

We will add a new field to store posts favorited by a user. Let edit _user.model.js_file to achieve this and add a new field right after password field:

favorites: {
         posts: [{
           type: Schema.Types.ObjectId,
           ref: 'Post'
         }]
       }

We will also add a function to use this field as well:

UserSchema.methods = {
     \_hashPassword(password) {
       ...
     },
     authenticateUser(password) {
       ...
     },
     createToken() {
       ...
     },
     toAuthJSON() {
       ...
     },
     toJSON() {
       ...
     },

     \_favorites: {
       async posts(postId) {
         if (this.favorites.posts.indexOf(postId) >= 0) {
           this.favorites.posts.remove(postId);
         } else {
           this.favorites.posts.push(postId);
         }
         return this.save();
       }
     }
   };

Extending Post Controller

Let’s add a function here as well to use this functionality we defined in the model. Start by using the import in_post.controller.js_file:

import User from ‘../users/user.model’;

Next, we call the Usermodel function:

export async function favoritePost(req, res) {
     try {
       const user = await User.findById(req.user.\_id);
       await user.\_favorites.posts(req.params.id);
       return res.sendStatus(HTTPStatus.OK);
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

Let’s finally modify our _post.routes.js_file to access this function:

routes.post(‘/:id/favorite’, authJwt, postController.favoritePost);

Time to test this route now. In Postman, make a GETrequest to the favorite API after picking a PostID from either Database or the Get all post API:

Next, we verify if this worked from MongoDB:

We only kept the object ID because this will save us from replicating the data. If you hit the same API again, you will see something strange, that the Post ID is now removed from the favorites in user model!

We are also keeping the favoriteCount in Post model. Let us make it work now. We will include this logic in Postmodel class:

PostSchema.statics = {
   createPost(args, user) {
     ...
   },
   list({ skip = 0, limit = 5 } = {}) {
     ...
   },

   incFavoriteCount(postId) {
     return this.findByIdAndUpdate(postId, { $inc: { favoriteCount: 1 } });
   },

   decFavoriteCount(postId) {
     return this.findByIdAndUpdate(postId, { $inc: { favoriteCount: -1 } });
   }
 };

The incFavoriteCount and decFavoriteCount methods first use Mongo’s findByIdAndUpdate method to find the Post ID and then use the $inc operator to add either 1 in case of increment or -1 in case of decrement the favorites.

Let’s modify the User model as well now. Add this import statement first:

import Post from ‘../posts/post.model’;

Then, modify the _favoritesmethod functionality here:

\_favorites: {
       async posts(postId) {
         if (this.favorites.posts.indexOf(postId) >= 0) {
           this.favorites.posts.remove(postId);
           await Post.decFavoriteCount(postId);
         } else {
           this.favorites.posts.push(postId);
           await Post.incFavoriteCount(postId);
         }

         return this.save();
       }
     }

Now the User model issue we stated above will resolve and the favoriteCount in Post model will also work:

If you hit the same API again and again, the result won’t change. Excellent! We have working APIs where a user can favorite a post as well.

A premium training course to learn to build apps with Node.js, Express, MongoDB, and friends. Start Learning Now →

Identifying if a Post is already a Favorite to User

the last section, we are able to register a new user in our application:

We are also able to allow a user to login into our application:

We were able to create a post related to a user:

Update a post:

And delete a Post as well:

In this section, we will be adding the following functionalities:

  • We will send them if the current post is favorite to the user or not so that front-end can make decisions based on this fact
  • We will make a route modification and work on Controller functions as well

Extending route

We just need to make very few modifications in our_post.route.js_file:

routes.get(‘/:id’, authJwt, postController.getPostById);
routes.get(‘/’, authJwt, postController.getPostsList);

We just added authJwt in these two existing lines. Once this is done, if I try to get Post list without Authorization header, we will get an error:

Extending the User model

Now, we will add more information to the post JSON if it is favorable to the current Authorizeduser.

Move to the _user.model.js_file and add this function in _favorites:

isPostIsFavorite(postId) {
     if (this.favorites.posts.indexOf(postId) >= 0) {
       return true;
     }
    return false;
    }

Move to the _post.controller.js_file now and modify the getPostByIdfunction:

export async function getPostById(req, res) {
     try {
       const promise = await Promise.all([
         User.findById(req.user.\_id),
           Post.findById(req.params.id).populate('user')
       ]);

       const favorite = promise[0].\_favorites.isPostIsFavorite(req.params.id);
       const post = promise[1];

       return res.status(HTTPStatus.OK).json({
         ...post.toJSON(),
         favorite
       });
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

Here, we just added a new field favorite which will be reflected in a Post API like this:

We will modify our getPostsListfunction as well to include a Promise and return the appropriate response:

export async function getPostsList(req, res) {
     const limit = parseInt(req.query.limit, 0);
     const skip = parseInt(req.query.skip, 0);
     try {
       const promise = await Promise.all([
         User.findById(req.user.\_id),
         Post.list({ limit, skip })
       ]);

       const posts = promise[1].reduce((arr, post) => {
         const favorite = promise[0].\_favorites.isPostIsFavorite(post.\_id);

         arr.push({
           ...post.toJSON(),
           favorite
         });

         return arr;
       }, []);

       return res.status(HTTPStatus.OK).json(posts);
     } catch (e) {
       return res.status(HTTPStatus.BAD\_REQUEST).json(e);
     }
   }

Let’s run this now and get all posts:

Excellent.

Conclusion

your will learn a lot of Node and API knowledge from this post but has more and more topic that we should know eg.secrity, rate limit, best practice I hope you enjoy for this.

Node.js, ExpressJs, MongoDB and Vue.js (MEVN Stack) Application Tutorial

Node.js, ExpressJs, MongoDB and Vue.js (MEVN Stack) Application Tutorial

In this tutorial, you'll learn how to integrate Vue.js with Node.js backend (using Express framework) and MongoDB and how to build application with Node.js, ExpressJs, MongoDB and Vue.js

In this tutorial, you'll learn how to integrate Vue.js with Node.js backend (using Express framework) and MongoDB and how to build application with Node.js, ExpressJs, MongoDB and Vue.js

Vue.js is a JavaScript framework with growing number of users. Released 4 years ago, it’s now one of the most populare front-end frameworks. There are some reasons why people like Vue.js. Using Vue.js is very simple if you are already familiar with HTML and JavaScript. They also provide clear documentation and examples, makes it easy for starters to learn the framework. Vue.js can be used for both simple and complex applications. If your application is quite complex, you can use Vuex for state management, which is officially supported. In addition, it’s also very flexible that yu can write template in HTML, JavaScript or JSX.

This tutorial shows you how to integrate Vue.js with Node.js backend (using Express framework) and MongoDB. As for example, we’re going to create a simple application for managing posts which includes list posts, create post, update post and delete post (basic CRUD functionality). I divide this tutorial into two parts. The first part is setting up the Node.js back-end and database. The other part is writing Vue.js code including how to build .vue code using Webpack.

Dependencies

There are some dependencies required for this project. Add the dependencies below to your package.json. Then run npm install to install these dependencies.

  "dependencies": {
    "body-parser": "~1.17.2",
    "dotenv": "~4.0.0",
    "express": "~4.16.3",
    "lodash": "~4.17.10",
    "mongoose": "~5.2.9",
    "morgan": "~1.9.0"
  },
  "devDependencies": {
    "axios": "~0.18.0",
    "babel-core": "~6.26.3",
    "babel-loader": "~7.1.5",
    "babel-preset-env": "~1.7.0",
    "babel-preset-stage-3": "~6.24.1",
    "bootstrap-vue": "~2.0.0-rc.11",
    "cross-env": "~5.2.0",
    "css-loader": "~1.0.0",
    "vue": "~2.5.17",
    "vue-loader": "~15.3.0",
    "vue-router": "~3.0.1",
    "vue-style-loader": "~4.1.2",
    "vue-template-compiler": "~2.5.17",
    "webpack": "~4.16.5",
    "webpack-cli": "^3.1.0"
  },

Project Structure

Below is the overview of directory structure for this project.

  app
    config
    controllers
    models
    queries
    routes
    views
  public
    dist
    src

The app directory contains all files related to server-side. The public directory contains two sub-directories: dist and src. dist is used for the output of build result, while src is for front-end code files.

Model

First, we define a model for Post using Mongoose. To make it simple, it only has two properties: title and content.

app/models/Post.js

  const mongoose = require('mongoose');

  const { Schema } = mongoose;

  const PostSchema = new Schema(
    {
      title: { type: String, trim: true, index: true, default: '' },
      content: { type: String },
    },
    {
      collection: 'posts',
      timestamps: true,
    },
  );

  module.exports = mongoose.model('Post', PostSchema);

Queries

After defining the model, we write some queries that will be needed in the controllers.

app/queries/posts.js

  const Post = require('../models/Post');

  /**
   * Save a post.
   *
   * @param {Object} post - Javascript object or Mongoose object
   * @returns {Promise.}
   */
  exports.save = (post) => {
    if (!(post instanceof Post)) {
      post = new Post(post);
    }

    return post.save();
  };

  /**
   * Get post list.
   * @param {object} [criteria] - Filter options
   * @returns {Promise.<Array.>}
   */
  exports.getPostList = (criteria = {}) => Post.find(criteria);

  /**
   * Get post by ID.
   * @param {string} id - Post ID
   * @returns {Promise.}
   */
  exports.getPostById = id => Post.findOne({ _id: id });

  /**
   * Delete a post.
   * @param {string} id - Post ID
   * @returns {Promise}
   */
  exports.deletePost = id => Post.findByIdAndRemove(id);

Controllers

We need API controllers for handling create post, get post listing, get detail of a post, update a post and delete a post.

app/controllers/api/posts/create.js

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.save(req.body)
    .then((post) => {
      if (!post) {
        return Promise.reject(new Error('Post not created'));
      }

      return res.status(200).send(post);
    })
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to create post');
    });

app/controllers/api/posts/delete.js

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.deletePost(req.params.id)
    .then(() => res.status(200).send())
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to delete post');
    });

app/controllers/api/posts/details.js

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.getPostById(req.params.id)
    .then((post) => {
      if (!post) {
        return Promise.reject(new Error('Post not found'));
      }

      return res.status(200).send(post);
    })
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to get post');
    });

app/controllers/api/posts/list.js

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.getPostList(req.params.id)
    .then(posts => res.status(200).send(posts))
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to get post list');
    });

app/controllers/api/posts/update.js

  const _ = require('lodash');

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.getPostById(req.params.id)
    .then(async (post) => {
      if (!post) {
        return Promise.reject(new Error('Post not found'));
      }

      const { title, content } = req.body;

      _.assign(post, {
        title, content
      });

      await postQueries.save(post);

      return res.status(200).send({
        success: true,
        data: post,
      })
    })
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to update post');
    });

Routes

We need to have some pages for user interaction and some API endpoints for processing HTTP requests. To make the app scalable, it’s better to separate the routes for pages and APIs.

app/routes/index.js

  const express = require('express');

  const routes = express.Router();

  routes.use('/api', require('./api'));
  routes.use('/', require('./pages'));

  module.exports = routes;


Below is the API routes.

app/routes/api/index.js

  const express = require('express');

  const router = express.Router();

  router.get('/posts/', require('../../controllers/api/posts/list'));
  router.get('/posts/:id', require('../../controllers/api/posts/details'));
  router.post('/posts/', require('../../controllers/api/posts/create'));
  router.patch('/posts/:id', require('../../controllers/api/posts/update'));
  router.delete('/posts/:id', require('../../controllers/api/posts/delete'));

  module.exports = router;


For the pages, in this tutorial, we use plain HTML file. You can easily replace it with any HTML template engine if you want. The HTML file contains a div whose id is app. Later, in Vue.js application, it will use the element with id app for rendering the content. What will be rendered on each pages is configured on Vue.js route on part 2 of this tutorial.

app/routes/pages/index.js

  const express = require('express');

  const router = express.Router();

  router.get('/posts/', (req, res) => {
    res.sendFile(`${__basedir}/views/index.html`);
  });

  router.get('/posts/create', (req, res) => {
    res.sendFile(`${__basedir}/views/index.html`);
  });

  router.get('/posts/:id', (req, res) => {
    res.sendFile(`${__basedir}/views/index.html`);
  });

  module.exports = router;

Below is the HTML file

app/views/index.html

  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8">
      <title>VueJS Tutorial by Woolha.com</title>
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" type="text/css" media="all" />
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
      <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    </head>
    <body>
      <div id="app"></div>
      <script src="/dist/js/main.js"></script>
    </body>
  </html>

Below is the main script of the application, you need to run this for starting the server-side application.

app/index.js

  require('dotenv').config();

  const bodyParser = require('body-parser');
  const express = require('express');
  const http = require('http');
  const mongoose = require('mongoose');
  const morgan = require('morgan');
  const path = require('path');

  const dbConfig = require('./config/database');
  const routes = require('./routes');

  const app = express();
  const port = process.env.PORT || 4000;

  global.__basedir = __dirname;

  mongoose.Promise = global.Promise;

  mongoose.connect(dbConfig.url, dbConfig.options, (err) => {
    if (err) {
      console.error(err.stack || err);
    }
  });

  /* General setup */
  app.use(morgan('dev'));
  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({ extended: true }));
  app.use(morgan('dev'));

  app.use('/', routes);

  const MAX_AGE = 86400000;

  // Select which directories or files under public can be served to users
  app.use('/', express.static(path.join(__dirname, '../public'), { maxAge: MAX_AGE }));

  // Error handler
  app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
    res.status(err.status || 500);

    if (err.status === 404) {
      res.locals.page = {
        title: 'Not Found',
        noIndex: true,
      };

      console.error(`Not found: ${req.url}`);

      return res.status(404).send();
    }

    console.error(err.stack || err);

    return res.status(500).send();
  });

  http
    .createServer(app)
    .listen(port, () => {
      console.info(`HTTP server started on port ${port}`);
    })
    .on('error', (err) => {
      console.error(err.stack || err);
    });

  process.on('uncaughtException', (err) => {
    if (err.name === 'MongoError') {
      mongoose.connection.emit('error', err);
    } else {
      console.error(err.stack || err);
    }
  });

  module.exports = app;

That’s all for the server side preparation. On the next part, we’re going to set up the Vue.js client-side application and build the code into a single JavaScript file ready to be loaded from HTML.

Then, we build the code using Webpack, so that it can be loaded from HTML. In this tutorial, we’re building a simple application with basic CRUD functionality for managing posts.

Create Vue.js Components

For managing posts, there are three components we’re going to create. The first one is for creating a new post. The second is for editing a post. The other is for managing posts (displaying list of posts and allow post deletion)

First, this is the component for creating a new post. It has one method createPost which validate data and send HTTP request to the server. We use axios for sending HTTP request.

public/src/components/Posts/Create.vue

  <template>
    <b-container>
      <h1 class="d-flex justify-content-center">Create a Post</h1>
      <p v-if="errors.length">
        <b>Please correct the following error(s):</b>
        <ul>
          <li v-for="error in errors">{{ error }}</li>
        </ul>
      </p>
      <b-form @submit.prevent>
        <b-form-group>
          <b-form-input type="text" class="form-control" placeholder="Title of the post" v-model="post.title"></b-form-input>
        </b-form-group>
        <b-form-group>
          <b-form-textarea class="form-control" placeholder="Write the content here" v-model="post.content"></b-form-textarea>
        </b-form-group>
        <b-button variant="primary" v-on:click="createPost">Create Post</b-button>
      </b-form>
    </b-container>
  </template>

  <script>
    import axios from 'axios';

    export default {
      data: () => ({
        errors: [],
        post: {
          title: '',
          content: '',
        },
      }),
      methods: {
        createPost(event) {
          if (event) {
            event.preventDefault();
          }

          if (!this.post.title) {
            this.errors = [];

            if (!this.post.title) {
              this.errors.push('Title required.');
            }

            return;
          }

          const url = 'http://localhost:4000/api/posts';
          const param = this.post;

          axios
            .post(url, param)
            .then((response) => {
              console.log(response);
              window.location.href = 'http://localhost:4000/posts';
            }).catch((error) => {
              console.log(error);
            });
        },
      }
    }
  </script>


Below is the component for editing a post. Of course, we need the current data of the post before editing it. Therefore, there’s fetchPost method called when the component is created. There’s also updatePost method which validate data and call the API for updating post.

public/src/components/Posts/Edit.vue

  <template>
    <b-container>
      <h1 class="d-flex justify-content-center">Edit a Post</h1>
      <p v-if="errors.length">
        <b>Please correct the following error(s):</b>
        <ul>
          <li v-for="error in errors">{{ error }}</li>
        </ul>
      </p>
      <b-form @submit.prevent>
        <b-form-group>
          <b-form-input type="text" class="form-control" placeholder="Title of the post" v-model="post.title"></b-form-input>
        </b-form-group>
        <b-form-group>
          <b-form-textarea class="form-control" placeholder="Write the content here" v-model="post.content"></b-form-textarea>
        </b-form-group>
        <b-button variant="primary" v-on:click="updatePost">Update Post</b-button>
      </b-form>
    </b-container>
  </template>

  <script>
    import axios from 'axios';

    export default {
      data: () => ({
        errors: [],
        post: {
          _id: '',
          title: '',
          content: '',
        },
      }),
      created: function() {
        this.fetchPost();
      },
      methods: {
        fetchPost() {
          const postId = this.$route.params.id;
          const url = `http://localhost:4000/api/posts/${postId}`;

          axios
            .get(url)
            .then((response) => {
              this.post = response.data;
              console.log('this.post;');
              console.log(this.post);
          });
        },
        updatePost(event) {
          if (event) {
            event.preventDefault();
          }

          if (!this.post.title) {
            this.errors = [];

            if (!this.post.title) {
              this.errors.push('Title required.');
            }

            return;
          }

          const url = `http://localhost:4000/api/posts/${this.post._id}`;
          const param = this.post;

          axios
            .patch(url, param)
            .then((response) => {
                console.log(response);
              window.alert('Post successfully saved');
            }).catch((error) => {
              console.log(error);
            });
        },
      }
    }
  </script>


For managing posts, we need to fetch the list of post first. Similar to the edit component, in this component, we have fetchPosts method called when the component is created. For deleting a post, there’s also a method deletePost. If post successfully deleted, the fetchPosts method is called again to refresh the post list.

public/src/components/Posts/List.vue

  <template>
    <b-container>
      <h1 class="d-flex justify-content-center">Post List</h1>
      <b-button variant="primary" style="color: #ffffff; margin: 20px;"><a href="/posts/create" style="color: #ffffff;">Create New Post</a></b-button>
      <b-container-fluid v-if="posts.length">
        <table class="table">
          <thead>
            <tr class="d-flex">
              <td class="col-8">Titleqqqqqqqqq</td>
              <td class="col-4">Actions</td>
            </tr>
          </thead>
          <tbody>
            <tr v-for="post in posts" class="d-flex">
              <td class="col-8">{{ post.title }}</td>
              <td class="col-2"><a v-bind:href="'http://localhost:4000/posts/' + post._id"><button type="button" class="btn btn-primary"><i class="fa fa-edit" aria-hidden="true"></i></button></a></td>
              <td class="col-2"><button type="button" class="btn btn-danger" v-on:click="deletePost(post._id)"><i class="fa fa-remove" aria-hidden="true"></i></button></td>
            </tr>
          </tbody>
        </table>
      </b-container-fluid>
    </b-container>
  </template>

  <script>
    import axios from 'axios';

    export default {
      data: () => ({
        posts: [],
      }),
      created: () => {
        this.fetchPosts();
      },
      methods: {
        fetchPosts() {
          const url = 'http://localhost:4000/api/posts/';

          axios
            .get(url)
            .then((response) => {
              console.log(response.data);
              this.posts = response.data;
          });
        },
        deletePost(id) {
          if (event) {
            event.preventDefault();
          }

          const url = `http://localhost:4000/api/posts/${id}`;
          const param = this.post;

          axios
            .delete(url, param)
            .then((response) => {
              console.log(response);
              console.log('Post successfully deleted');

              this.fetchPosts();
            }).catch((error) => {
              console.log(error);
            });
        },
      }
    }
  </script>


All of the components above are wrapped into a root component which roles as the basic template. The root component renders the navbar which is same across all components. The component for each routes will be rendered on router-view.

public/src/App.vue

  <template>
    <div>
      <b-navbar toggleable="md" type="dark" variant="dark">
        <b-navbar-toggle target="nav_collapse"></b-navbar-toggle>
        <b-navbar-brand to="/">My Vue App</b-navbar-brand>
        <b-collapse is-nav id="nav_collapse">
          <b-navbar-nav>
            <b-nav-item to="/">Home</b-nav-item>
            <b-nav-item to="/posts">Manage Posts</b-nav-item>
          </b-navbar-nav>
        </b-collapse>
      </b-navbar>
      <!-- routes will be rendered here -->
      <router-view />
    </div>
  </template>

  <script>

  export default {
    name: 'app',
    data () {},
    methods: {}
  }
  </script>


For determining which component should be rendered, we use Vue.js’ router. For each routes, we need to define the path, component name and the component itself. A component will be rendered if the current URL matches the path.

public/src/router/index.js

  import Vue from 'vue'
  import Router from 'vue-router'

  import CreatePost from '../components/Posts/Create.vue';
  import EditPost from '../components/Posts/Edit.vue';
  import ListPost from '../components/Posts/List.vue';

  Vue.use(Router);

  let router = new Router({
    mode: 'history',
    routes: [
      {
        path: '/posts',
        name: 'ListPost',
        component: ListPost,
      },
      {
        path: '/posts/create',
        name: 'CreatePost',
        component: CreatePost,
      },
      {
        path: '/posts/:id',
        name: 'EditPost',
        component: EditPost,
      },
    ]
  });

  export default router;


Lastly, we need a main script as the entry point which imports the main App component and the router. Inside, it creates a new Vue instance

webpack.config.js

  import BootstrapVue from 'bootstrap-vue';
  import Vue from 'vue';

  import App from './App.vue';
  import router from './router';

  Vue.use(BootstrapVue);
  Vue.config.productionTip = false;
  new Vue({
    el: '#app',
    router,
    render: h => h(App),
  });

Configure Webpack

For building the code into a single JavaSript file. Below is the basic configuration for Webpack 4.

webpack.config.js

  const { VueLoaderPlugin } = require('vue-loader');

  module.exports = {
    entry: './public/src/main.js',
    output: {
      path: `${__dirname}/public/dist/js/`,
      filename: '[name].js',
    },
    resolve: {
      modules: [
        'node_modules',
      ],
      alias: {
        // vue: './vue.js'
      }
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'vue-style-loader',
            'css-loader'
          ]
        },
        {
          test: /\.vue$/,
          loader: 'vue-loader',
          options: {
            loaders: {
            }
            // other vue-loader options go here
          }
        },
        {
          test: /\.js$/,
          loader: 'babel-loader',
          exclude: /node_modules/
        },
      ]
    },
    plugins: [
      new VueLoaderPlugin(),
    ]

After that, run ./node_modules/webpack/bin/webpack.js. You can add the command to the scripts section of package.json, so you can run Webpack with a shorter command npm run build, as examplified below.

  "dependencies": {
    ...
  },
  "devDependencies": {
    ...
  },
  "scripts": {
    "build": "./node_modules/webpack/bin/webpack.js",
    "start": "node app/index.js"
  },

Finally, you can start to try the application. This code is also available on Woolha.com’s Github.

MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat App

MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat App

The comprehensive tutorial on MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat Application

The comprehensive tutorial on MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat Application

MEVN Tutorial: The comprehensive tutorial on MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat Application. Previously we have a tutorial on build chat application using MEAN Stack, now we build this chat application using MEVN (MongoDB, Express.js, Vue.js 2, Node.js) Stack. The different just now we use Vue.js 2 and Axios, we keep using MongoDB, Node.js, Express, and Socket.io.

Table of Contents:
  • Create a New Vue.js 2 Application using Vue-CLI
  • Install Express.js as RESTful API Server
  • Install and Configure Mongoose.js
  • Create REST API for Accessing Chat Data
  • Create Mongoose.js Model for Room and Chat
  • Create Vue.js 2 Component and Routing
  • Add Module for RESTful API Access and Styling UI
  • Modify Vue.js 2 Component for Room List
  • Modify Vue.js 2 Component for Add Room
  • Modify Vue.js 2 Component for Join Room
  • Modify Vue.js 2 Component for Chat Room
  • Integrate Socket.io With Existing Non-Real-time Chat Application
  • Run and Test The Chat Application

The scenario is very simple, just the rooms and the chats for each room. The first page will show the list of the rooms. After the user enters the room and fills the username or nickname then the user enters the chats with other users.

.The following tools, frameworks, and modules are required for this tutorial:

  1. Node.js (use recommended version)
  2. Express.js
  3. MongoDB
  4. Mongoose.js
  5. Vue.js
  6. Vue-CLI
  7. Socket IO
  8. Axios
  9. Terminal (Mac/Linux) or Node.js Command Line (Windows)
  10. IDE or Text Editor (we use Atom)

We assume that you have already installed Node.js and able to run Node.js command line (Windows) or npm on the terminal (MAC/Linux). Open the terminal or Node command line then type this command to install vue-cli.

sudo npm install -g vue-cli

That where we start the tutorial. We will create the MEVN stack Chat application using vue-cli.

1. Create a New Vue.js 2 Application using Vue-CLI

To create a new Vue.js 2 application using vue-cli simply type this command from terminal or Node command line.

vue init webpack mevn-chat

There will be a lot of questions, just leave it as default by always pressing enter key. Next, go to the newly created Vue.js project folder then install all default required modules by type this command.

cd ./mevn-chat

Now, check the Vue.js 2 application by running the application using this command.

npm run dev

Open your browser then go to localhost:8080 and you should see this page when everything still on the track.

2. Install Express.js as RESTful API Server

Close the running Vue.js 2 app first by press ctrl+c then type this command for adding Express.js modules and its dependencies.

npm install --save express body-parser morgan body-parser serve-favicon

Next, create a new folder called bin then add a file called www on the root of the Vue.js project folder.

mkdir bin
touch bin/www

Open and edit www file then add these lines of codes that contains configuration for an HTTP server, PORT, and error handling.

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('mean-app:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

Next, change the default server what run by npm command. Open and edit package.json then replace startvalue inside scripts.

"scripts": {
  "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
  "start": "npm run build && node ./bin/www",
  "unit": "jest --config test/unit/jest.conf.js --coverage",
  "e2e": "node test/e2e/runner.js",
  "test": "npm run unit && npm run e2e",
  "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
  "build": "node build/build.js"
},

Next, create app.js in the root of project folder.

touch app.js

Open and edit app.js then add this lines of codes.

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var bodyParser = require('body-parser');

var room = require('./routes/room');
var chat = require('./routes/chat');
var app = express();

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({'extended':'false'}));
app.use(express.static(path.join(__dirname, 'dist')));
app.use('/rooms', express.static(path.join(__dirname, 'dist')));
app.use('/api/room', room);
app.use('/api/chat', chat);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

Next, create routes folder then create routes file for the room and chat.

mkdir routes
touch routes/room.js
touch routes/chat.js

Open and edit routes/room.js file then add this lines of codes.

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.send('Express RESTful API');
});

module.exports = router;

Do the same way with routes/chat.js. Now, run the server using this command.

npm start

You will see the previous Vue.js landing page when you point your browser to [http://localhost:3000](http://localhost:3000 "http://localhost:3000"). When you change the address to [http://localhost:3000/api/room](http://localhost:3000/api/room "http://localhost:3000/api/room") or [http://localhost:3000/api/chat](http://localhost:3000/api/chat "http://localhost:3000/api/chat") you will see this page.

3. Install and Configure Mongoose.js

We need to access data from MongoDB. For that, we will install and configure Mongoose.js. On the terminal type this command after stopping the running Express server.

npm install --save mongoose bluebird

Open and edit app.js then add this lines after another variable line.

var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
mongoose.connect('mongodb://localhost/mevn-chat', { promiseLibrary: require('bluebird') })
  .then(() =>  console.log('connection succesful'))
  .catch((err) => console.error(err));

Now, run MongoDB server on different terminal tab or command line or run from the service.

mongod

Next, you can test the connection to MongoDB run again the Node application and you will see this message on the terminal.

connection succesful

If you are still using built-in Mongoose Promise library, you will get this deprecated warning on the terminal.

(node:42758) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html

That’s the reason why we added bluebird modules and register it as Mongoose Promise library.

4. Create Mongoose.js Model for Room and Chat

Add a models folder on the root of project folder for hold Mongoose.js model files then add Javascript file for Room and Chat.

mkdir models
touch models/Room.js
touch models/Chat.js

Next, open and edit models/Room.js then add this lines of codes.

var mongoose = require('mongoose'), Schema = mongoose.Schema;

var RoomSchema = new mongoose.Schema({
  room_name: String,
  created_date: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Room', RoomSchema);

Open and edit models/Chat.js then add this lines of codes.

var mongoose = require('mongoose'), Schema = mongoose.Schema;

var ChatSchema = new mongoose.Schema({
  room : { type: Schema.Types.ObjectId, ref: 'Room' },
  nickname: String,
  message: String,
  created_date: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Chat', ChatSchema);
5. Create Vue.js Component and Routing

Open and edit again routes/room.js then replace all codes with this.

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Room = require('../models/Room.js');

/* GET ALL ROOMS */
router.get('/', function(req, res, next) {
  Room.find(function (err, products) {
    if (err) return next(err);
    res.json(products);
  });
});

/* GET SINGLE ROOM BY ID */
router.get('/:id', function(req, res, next) {
  Room.findById(req.params.id, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* SAVE ROOM */
router.post('/', function(req, res, next) {
  Room.create(req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* UPDATE ROOM */
router.put('/:id', function(req, res, next) {
  Room.findByIdAndUpdate(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* DELETE ROOM */
router.delete('/:id', function(req, res, next) {
  Room.findByIdAndRemove(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

module.exports = router;

Open and edit again routes/chat.js then replace all codes with this.

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Chat = require('../models/Chat.js');

/* GET ALL CHATS */
router.get('/', function(req, res, next) {
  Chat.find(function (err, products) {
    if (err) return next(err);
    res.json(products);
  });
});

/* GET SINGLE CHAT BY ID */
router.get('/:id', function(req, res, next) {
  Chat.findById(req.params.id, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* SAVE CHAT */
router.post('/', function(req, res, next) {
  Chat.create(req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* UPDATE CHAT */
router.put('/:id', function(req, res, next) {
  Chat.findByIdAndUpdate(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* DELETE CHAT */
router.delete('/:id', function(req, res, next) {
  Chat.findByIdAndRemove(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

module.exports = router;

Run again the Express server then open the other terminal or command line to test the Restful API by type this command.

curl -i -H "Accept: application/json" localhost:3000/api/room

If that command return response like below then REST API is ready to go.

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 2
ETag: W/"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w"
Date: Sun, 05 Aug 2018 13:11:30 GMT
Connection: keep-alive

[]

Now, let’s populate Room collection with initial data that sent from RESTful API. Run this command to populate it.

curl -i -X POST -H "Content-Type: application/json" -d '{ "room_name":"Javascript" }' localhost:3000/api/room

You will see this response to the terminal if success.

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 109
ETag: W/"6d-OGpcih/JWvJGrYAhMP+KBYQOvNQ"
Date: Sun, 05 Aug 2018 13:35:50 GMT
Connection: keep-alive

{"_id":"5b66fd3581b9291558dc90b7","room_name":"Javascript","created_date":"2018-08-05T13:35:49.803Z","__v":0}

6. Create Vue.js 2 Component and Routing

Now, it’s time for Vue.js 2 or front end part. First, create or add the component of the room list, add a room, join a room, chat room. Create all of those files into the components folder.

touch src/components/RoomList.vue
touch src/components/AddRoom.vue
touch src/components/JoinRoom.vue
touch src/components/ChatRoom.vue

Now, open and edit src/router/index.js then add the import for all above new components.

import Vue from 'vue'
import Router from 'vue-router'
import RoomList from '@/components/RoomList'
import AddRoom from '@/components/AddRoom'
import JoinRoom from '@/components/JoinRoom'
import ChatRoom from '@/components/ChatRoom'

Add the router to each component or page.

export default new Router({
  routes: [
    {
      path: '/',
      name: 'RoomList',
      component: RoomList
    },
    {
      path: '/add-room',
      name: 'AddRoom',
      component: AddRoom
    },
    {
      path: '/join-room/:id',
      name: 'JoinRoom',
      component: JoinRoom
    },
    {
      path: '/chat-room/:id/:nickname',
      name: 'ChatRoom',
      component: ChatRoom
    }
  ]
})

7. Add Axios and Bootstrap-Vue

For UI or styling, we are using Bootstrap Vue. BootstrapVue use to build responsive, mobile-first projects on the web using Vue.js and the world's most popular front-end CSS library Bootstrap v4. To install Bootstrap-Vue type this command on the terminal.

npm i bootstrap-vue [email protected]

Open and edit src/main.js then add the imports for Bootstrap-Vue.

import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
import App from './App'
import router from './router'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Add this line after Vue.config.

Vue.use(BootstrapVue)

Next, we are using Axio for accessing REST API provided by Express.js. Axios is a promise-based HTTP client for the browser and node.js. To install it, in the terminal type this command.

npm install axios --save

8. Modify Vue.js 2 Component for Room List

Now, open and edit src/components/RoomList.vue then add this lines of codes.

<template>
  <b-row>
    <b-col cols="12">
      <h2>
        Room List
        <b-link href="#/add-room">(Add Room)</b-link>
      </h2>
      <b-table striped hover :items="rooms" :fields="fields">
        <template slot="actions" scope="row">
          <b-btn size="sm" @click.stop="join(row._id)">Join</b-btn>
        </template>
      </b-table>
      <ul v-if="errors && errors.length">
        <li v-for="error of errors">
          {{error.message}}
        </li>
      </ul>
    </b-col>
  </b-row>
</template>

<script>

import axios from 'axios'

export default {
  name: 'BookList',
  data () {
    return {
      fields: {
        room_name: { label: 'Room Name', sortable: true, 'class': 'text-center' },
        created_date: { label: 'Created Date', sortable: true },
        actions: { label: 'Action', 'class': 'text-center' }
      },
      rooms: [],
      errors: []
    }
  },
  created () {
    axios.get(`http://localhost:3000/api/room`)
    .then(response => {
      this.rooms = response.data
    })
    .catch(e => {
      this.errors.push(e)
    })
  },
  methods: {
    join (id) {
      this.$router.push({
        name: 'JoinRoom',
        params: { id: id }
      })
    }
  }
}
</script>

There are template and script in one file. The template block contains HTML tags. Script block contains variables, page lifecycle and methods or functions.

9. Modify Vue.js 2 Component for Add Room

Now, open and edit src/components/AddRoom.vue then add this lines of codes.

<template>
  <b-row>
    <b-col align-self="start">&nbsp;</b-col>
    <b-col cols="6" align-self="center">
      <h2>
        Add Room
        <b-link href="#/">(Room List)</b-link>
      </h2>
      <b-form @submit="onSubmit">
        <b-form-group id="fieldsetHorizontal"
                  horizontal
                  :label-cols="4"
                  breakpoint="md"
                  label="Enter Room Name">
          <b-form-input id="room_name" :state="state" v-model.trim="room.room_name"></b-form-input>
        </b-form-group>
        <b-button type="submit" variant="primary">Save</b-button>
      </b-form>
    </b-col>
    <b-col align-self="end">&nbsp;</b-col>
  </b-row>
</template>

<script>

import axios from 'axios'

export default {
  name: 'AddRoom',
  data () {
    return {
      room: {}
    }
  },
  methods: {
    onSubmit (evt) {
      evt.preventDefault()
      axios.post(`http://localhost:3000/api/room`, this.room)
      .then(response => {
        this.$router.push({
          name: 'RoomList'
        })
      })
      .catch(e => {
        this.errors.push(e)
      })
    }
  }
}
</script>

That code contains the template for room form, the script that contains Vue.js 2 codes for hold room model and methods for saving room to RESTful API.

10. Modify Vue.js 2 Component for Join Room

Now, open and edit src/components/JoinRoom.vue then add this lines of codes.

<template>
  <b-row>
    <b-col cols="6">
      <h2>
        Join Room
        <b-link href="#/">(Room List)</b-link>
      </h2>
      <b-form @submit="onSubmit">
        <b-form-group id="fieldsetHorizontal"
                  horizontal
                  :label-cols="4"
                  breakpoint="md"
                  label="Enter Nickname">
          <b-form-input id="nickname" :state="state" v-model.trim="chat.nickname"></b-form-input>
        </b-form-group>
        <b-button type="submit" variant="primary">Join</b-button>
      </b-form>
    </b-col>
  </b-row>
</template>

<script>

import axios from 'axios'

export default {
  name: 'JoinRoom',
  data () {
    return {
      chat: {}
    }
  },
  methods: {
    onSubmit (evt) {
      evt.preventDefault()
      this.chat.room = this.$route.params.id
      this.chat.message = this.chat.nickname + ' join the room'
      axios.post(`http://localhost:3000/api/chat`, this.chat)
      .then(response => {
        this.$router.push({
          name: 'ChatRoom',
          params: { id: this.$route.params.id, nickname: response.data.nickname }
        })
      })
      .catch(e => {
        this.errors.push(e)
      })
    }
  }
}
</script>

That code contains the template for join room form, the script that contains Vue.js 2 codes for hold chat model and methods for saving room to RESTful API.

11. Modify Vue.js 2 Component for Chat Room

Now, open and edit src/components/JoinRoom.vue then add this lines of codes.

<template>
  <b-row>
    <b-col cols="12">
      <h2>
        Chat Room
      </h2>
      <b-list-group class="panel-body">
        <b-list-group-item v-for="(item, index) in chats" class="chat">
          <div class="left clearfix" v-if="item.nickname === nickname">
            <b-img left src="http://placehold.it/50/55C1E7/fff&text=ME" rounded="circle" width="75" height="75" alt="img" class="m-1" />
            <div class="chat-body clearfix">
              <div class="header">
                <strong class="primary-font">{{ item.nickname }}</strong> <small class="pull-right text-muted">
                <span class="glyphicon glyphicon-time"></span>{{ item.created_date }}</small>
              </div>
              <p>{{ item.message }}</p>
            </div>
          </div>
          <div class="right clearfix" v-else>
            <b-img right src="http://placehold.it/50/55C1E7/fff&text=U" rounded="circle" width="75" height="75" alt="img" class="m-1" />
            <div class="chat-body clearfix">
              <div class="header">
                <strong class="primary-font">{{ item.nickname }}</strong> <small class="pull-right text-muted">
                <span class="glyphicon glyphicon-time"></span>{{ item.created_date }}</small>
              </div>
              <p>{{ item.message }}</p>
            </div>
          </div>
        </b-list-group-item>
      </b-list-group>
      <ul v-if="errors && errors.length">
        <li v-for="error of errors">
          {{error.message}}
        </li>
      </ul>
      <b-form @submit="onSubmit" class="chat-form">
        <b-input-group prepend="Message">
          <b-form-input id="message" :state="state" v-model.trim="chat.message"></b-form-input>
          <b-input-group-append>
            <b-btn type="submit" variant="info">Send</b-btn>
          </b-input-group-append>
        </b-input-group>
      </b-form>
    </b-col>
  </b-row>
</template>

<script>

import axios from 'axios'

export default {
  name: 'ChatRoom',
  data () {
    return {
      chats: [],
      errors: [],
      nickname: this.$route.params.nickname,
      chat: {}
    }
  },
  created () {
    axios.get(`http://localhost:3000/api/chat/` + this.$route.params.id)
    .then(response => {
      this.chats = response.data
    })
    .catch(e => {
      this.errors.push(e)
    })
  },
  methods: {
    logout (id) {
      this.$router.push({
        name: 'JoinRoom',
        params: { id: id }
      })
    },
    onSubmit (evt) {
      evt.preventDefault()
      this.chat.room = this.$route.params.id
      this.chat.nickname = this.$route.params.nickname
      axios.post(`http://localhost:3000/api/chat`, this.chat)
      .then(response => {
        // this.$router.push({
        //   name: 'ChatRoom',
        //   params: { id: this.$route.params.id, nickname: response.data.nickname }
        // })
      })
      .catch(e => {
        this.errors.push(e)
      })
    }
  }
}
</script>

<style>
  .chat .left .chat-body {
    text-align: left;
    margin-left: 100px;
  }

  .chat .right .chat-body {
    text-align: right;
    margin-right: 100px;
  }

  .chat .chat-body p {
    margin: 0;
    color: #777777;
  }

  .panel-body {
    overflow-y: scroll;
    height: 350px;
  }

  .chat-form {
    margin: 20px auto;
    width: 80%;
  }
</style>

That code contains the template of the main chat application consist of chat list and sends message form.

12. Integrate Socket.io With Existing Non-Real-time Chat Application

Previous steps show you a regular and non-realtime transaction chat application. Now, we will make it real-time by using Socket.io. First, install socket.io module by type this command.

npm install --save socketio socket.io-client

Next, open and edit routes/chat.js then declare the Socket IO and http module.

var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);

Add this lines of codes for Socket IO functions.

server.listen(4000);

// socket io
io.on('connection', function (socket) {
  console.log('User connected');
  socket.on('disconnect', function() {
    console.log('User disconnected');
  });
  socket.on('save-message', function (data) {
    console.log(data);
    io.emit('new-message', { message: data });
  });
});

In that code, we are running Socket.io to listen for ‘save-message’ that emitted from the client and emit ‘new-message’ to the clients. Next, open and edit src/components/JoinRoom.vue then add this import.

import * as io from 'socket.io-client'

Declare Socket IO variable.

data () {
  return {
    chat: {},
    socket: io('http://localhost:4000')
  }
},

Add Socket IO emit function after successful join room.

axios.post(`http://localhost:3000/api/chat`, this.chat)
.then(response => {
  this.socket.emit('save-message', { room: this.chat.room, nickname: this.chat.nickname, message: 'Join this room', created_date: new Date() });
  this.$router.push({
    name: 'ChatRoom',
    params: { id: this.$route.params.id, nickname: response.data.nickname }
  })
})
.catch(e => {
  this.errors.push(e)
})

Next, open and edit src/components/ChatRoom.vue then add this imports and use as Vue module.

import Vue from 'vue'
import * as io from 'socket.io-client'
import VueChatScroll from 'vue-chat-scroll'
Vue.use(VueChatScroll)

Declare Socket IO variable.

data () {
  return {
    chats: [],
    errors: [],
    nickname: this.$route.params.nickname,
    chat: {},
    socket: io('http://localhost:4000')
  }
},

Add this Socket IO on function to created method.

created () {
  axios.get(`http://localhost:3000/api/chat/` + this.$route.params.id)
  .then(response => {
    this.chats = response.data
  })
  .catch(e => {
    this.errors.push(e)
  })

  this.socket.on('new-message', function (data) {
    if(data.message.room === this.$route.params.id) {
      this.chats.push(data.message)
    }
  }.bind(this))
},

Add Logout function inside methods and add Socket IO emit method in the POST response.

methods: {
  logout () {
    this.socket.emit('save-message', { room: this.chat.room, nickname: this.chat.nickname, message: this.chat.nickname + ' left this room', created_date: new Date() });
    this.$router.push({
      name: 'RoomList'
    })
  },
  onSubmit (evt) {
    evt.preventDefault()
    this.chat.room = this.$route.params.id
    this.chat.nickname = this.$route.params.nickname
    axios.post(`http://localhost:3000/api/chat`, this.chat)
    .then(response => {
      this.socket.emit('save-message', response.data)
      this.chat.message = ''
    })
    .catch(e => {
      this.errors.push(e)
    })
  }
}

Finally, to make Chat list always scroll to the bottom of Chat element add install this module.

npm install --save vue-chat-scroll

That module already imported and declared above. Next, add to the Chat element.

<b-list-group class="panel-body" v-chat-scroll>
  ...
</b-list-group>

13. Run and Test The MEVN (Vue.js 2) Chat Application**

To run this MEVN (Vue.js 2) Chat Application locally, make sure MongoDB service is running. Type this command to build the Vue.js 2 application then run the Express.js application.

npm start

Next, open the different browser (ex: Chrome and Firefox) then go to the localhost:3000 on both of the browsers. You will see this page and you can start Chat.

That it’s, the MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat App. You can find the full working source code on our GitHub.

Thanks!

=============================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ MERN Stack Front To Back: Full Stack React, Redux & Node.js

☞ The Complete Node.js Developer Course (3rd Edition)

☞ Angular & NodeJS - The MEAN Stack Guide

☞ NodeJS - The Complete Guide (incl. MVC, REST APIs, GraphQL)

☞ Docker for Node.js Projects From a Docker Captain

☞ Intro To MySQL With Node.js - Learn To Use MySQL with Node!

☞ Node.js Absolute Beginners Guide - Learn Node From Scratch

☞ React Node FullStack - Social Network from Scratch to Deploy

☞ Selenium WebDriver - JavaScript nodeJS webdriver IO & more!

☞ Complete Next.js with React & Node - Beautiful Portfolio App

☞ Build a Blockchain & Cryptocurrency | Full-Stack Edition

How to Use a SQL Like and Regex Search in MongoDB and Node.JS

How to Use a SQL Like and Regex Search in MongoDB and Node.JS

In this article we will know how to use Regex to search in MongoDB like the SQL Like Statement

In this article we will know how to use Regex to search in MongoDB like the SQL Like Statement

To select the documents from a collection, you can use the db.collection.find() method. To select all documents in the collection, pass an empty document as the query filter document to the method.

In the shell, copy and paste the following to return all documents in the members collection.

db.members.find({})

To format the results, append the .pretty() to the find operation:

db.members.find({}).pretty()

Searching for Word Using Regex

Now that we are using .find() to query our collection, we can actually modify our syntax ever so slightly and begin searching for matches based on a word or phrase that may be a partial match within a given field, similar to the LIKE operator for SQL engines.

The trick is to utilize regular expressions (or regex for short), which is basically a text string that defines a search pattern. There are a number of regex engines that are written in slightly different syntax, but the fundamentals are all basically the same, and in this case, MongoDB uses the Perl Regex (PCRE) engine.

At the most basic level, a regex expression is a string (series of characters) enclosed on both sides by a single slash (/).

For example, if we want to use regex to perform the same query as above and find out how many members serve Neha, we can replace our string "Neha" with /Neha/ instead:

db.members.find( { name: /Neha/ } )

But imagine we want to find the number of restaurants where borough starts with the first three characters "Neha". We’d modify our regex very slightly, like so:

db.members.find( { name: /^Neha/ } )

The caret character (^) specifies the location in our string which should be the beginning, so if we had a document where those three letters were in the middle of the field, we wouldn’t get a match.

This informs the regex engine that we want to the search to be case insensitive, matching regardless of upper or lowercase. We also added the special i flag following our regex closing slash (/):

db.members.find( { name: /Neha/i } )

Using variable regex with MongoDB in Node.JS

var search ='Neha';

db.members.find(name: new RegExp(search)) //For substring search, case sensitive. 

db.members.find(name: new RegExp('^' + search + '$')) //For exact search, case sensitive

db.members.find(name: new RegExp(search, ‘i')) //For substring search, case insensitive

db.members.find(name: new RegExp('^' +search + '$', 'i')); //For exact search, case insensitive

Happy Coding!

Please like and share so I have the motivation to continue sharing!