If you are a developer or just starting your awesome journey in the field of web development then probably you are familiar with the term REST APIs. Behind every web or mobile applications, there is a great backend. For a modern full-stack developer or backend developer, it is necessary to know how to build a scalable Node.js REST API for their application. 

In this tutorial, you will learn how to build a scalable REST API using the Node.js, express.js, and MongoDB. Before going further with this tutorial, make sure you have basic knowledge about node.js and how to use the express.js framework and connect MongoDB with the node.js application.

What Is REST

REST is an acronym of Representational State Transfer which is a web standard-based architecture and uses the HTTP protocol. It accesses and manipulates the operation of the data using stateless operation.

Below are the five most common HTTP methods which are mostly used by the developers to create a REST API

  1. GET: used for fetching the resources from the database
  2. POST: used for creating the new resources into the database
  3. PUT: used for updating/modifying the resources into the database
  4. DELETE: used for deleting the resources from the database
  5. PATCH: Mostly its behave as same as PUT. Its totally depends upon the developers how he/she wants to build the REST API

Apart from the above five methods of HTTP, there are also lots of methods which is used while making scalable node.js REST APIs. If you want to know about the HTTP methods then you can learn it from here.

How To Structure Scalable Node.js REST API

It totally depends upon the individual developers, a group of developers or organizations that how they want to structure their API. For this tutorial, I am going to use the most famous MVC architecture to build our node.js REST API. But there are many developers or community who like to build their API based on the feature of the application.

What is MVC Architecture

MVC architecture is one of the most famous architecture patterns in web development. In the MVC architecture, we divide our application into Model, View, and Controller. In the Model, we write all the database related queries. In the controller, we write about the logic. In the view, we write the user interface and how to display the data.

But we will extend this to a little bit and add few more directories like Routes, Services, and Middlewares. In this tutorial, we will write all our business logic into the services folder and then use it into our controller. It is best practice to keep all our business logic separate from the controllers.

At the end of the tutorial, your application directory must look like below

├── configs
│   ├── app.js
│   ├── config
│   │   ├── config.js
│   │   └── local.js
│   └── db.js
├── controllers
│   └── apis
│       └── user.js
├── models
│   └── user.js
├── package.json
├── package-lock.json
├── routes
│   ├── apis
│   │   ├── index.js
│   │   └── v1.js
│   └── index.js
├── server.js
└── services
    └── users
        └── user.js

9 directories, 13 files

Most of the time developers don’t think about the scalability of the application and miss one of the most important things while designing the REST API architecture of the application. Above structure might be puzzled your mind but it is a good practice in the web development to keep the things organized so that in the future you can scale easily without changing too much in the code.

Setup Node.js Application

In this tutorial, we are going to use node.js most famous framework Express.js. It provides lots of features like routing, middlewares, HTTP and other tons of things which is mandatory for our node.js REST API.

First, run the below command and create a package.json file

npm init

Note: make sure while creating the package.json file, you name your index file as server.js instead of default index.js

After creating the package.json file, run the below command to install expres.js, mongoose, and other tools which are required.

npm install express mongoose body-parser --save

npm install babel-cli babel-loader babel-preset-es2015 nodemon webpack webpack-cli --save-dev

The first line will install the required dependencies and the second line will installed tools only for development purpose. If you are not familiar with the above words then first you need to learn what are they and why we are using them. Below I am giving a short introduction to all of them.

  • Express.js:  is a node.js most famous and widely used frameworks to develop the application.
  • Mongoose: is a MongoDB ORM which provides easy to use syntax to query the data from the database.
  • body-parser: is used parse all the request coming in the application.
  • babel-cli: is used to make our application ES6 code to the backward-compatible version of the javascript.
  • webpack: is used to bundle the javascript files for the production.
  • nodemon: is used to start our server every time when there is any change in the code. It is only for the development purpose that’s why we install it into the devDependencies.

Till now we install all the dependencies for our application. Now let’s move further and create a server.js file for the application. This is the root file of the application and all of the request coming in the application control by this file. Paste below few lines of code into this file


/*******
* server.js file
********/ 

const server = require('./configs/app')();
const config = require('./configs/config/config');
const db = require('./configs/db');

//create the basic server setup 
server.create(config, db);

//start the server
server.start();

In the above file, we are creating three variable server, config,and db and importing it from the config folder. We are calling the create function of the app.js file to establish the required things for the server.

Generally, in other tutorials, you will see that most of the developers write all their code into a single file which is not good for scalability and any enterprise-level application.

Our server.js file is ready, now let’s create others file which we are using in the server.js file. First, we will create a folder configs and into this folder, we will create another folder config and two file app.js and db.js.

Paste the below code into the db.js file.


/************
*  db.js file
*************/

module.exports = {
  'secret': 'restapisecret',
  'database': 'mongodb://127.0.0.1:27017/restAPI'
};

The db.js file is a simple js file which contains our MongoDB URL and a secret key and then exporting it so that we can use it in others file.

In the app.js file, we will write all the code related to server configuration. Below is the app.js file, copy the code and paste it into your app.js file.

/*********
* app.js file
*********/


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

module.exports = function () {
    let server = express(),
        create,
        start;

    create = (config, db) => {
        let routes = require('../routes');
        // set all the server things
        server.set('env', config.env);
        server.set('port', config.port);
        server.set('hostname', config.hostname);
        
        // add middleware to parse the json
        server.use(bodyParser.json());
        server.use(bodyParser.urlencoded({
            extended: false
        }));

        //connect the database
        mongoose.connect(
            db.database,
            { 
                useNewUrlParser: true,
                useCreateIndex: true
            }
        );

        // Set up routes
        routes.init(server);
    };

    
    start = () => {
        let hostname = server.get('hostname'),
            port = server.get('port');
        server.listen(port, function () {
            console.log('Express server listening on - http://' + hostname + ':' + port);
        });
    };
    return {
        create: create,
        start: start
    };
};

In the above file, first, we are importing all the dependencies needed for the application. Then we are exporting anonymous functions from this file.

Inside the anonymous function, we are creating three variables server, create and start.

In the server variable, we are assigning the express object. The create and start variable has a function which we are using in our server.js file. The create function configure all the setup for this application and while start function will start our application.

In the create function, we are using the config variable but till now we didn’t create a file for this so let’s create a config.js file in the config folder and paste below code into this file.

/*******
* config.js file
*******/

const _ = require('lodash');
const env = process.env.NODE_ENV || 'local';
const envConfig = require('./' + env);
let defaultConfig = {
  env: env
};
module.exports = _.merge(defaultConfig, envConfig);

You will see that in the above file we are requiring the env file which is dependent on the environment like local, testing, staging or production. For a large application, It is always a good practice to keep keys based on the environment type. For this tutorial, we are only creating a local file which you can modify according to your need.

Create a local.js file inside the config folder and paste the below code and which export hostname and port inside the localConfig object.

/*********
* local.js
*********/

let localConfig = {
  hostname: 'localhost',
  port: 3000
};

module.exports = localConfig;

Build Node.js REST API

Now we are done with the basic setup of the application. Let’s move further and build other things of the API.

Let’s create models, controllers, routes and services folder of the application.

Create Routes for the Node.js REST API

First, let’s start with the routes of the API and create an index.js file inside the routes folder and paste the below code


/**********
* index.js file (for routes)
**********/

const apiRoute = require('./apis');

const init = (server) => {
    server.get('*', function (req, res, next) {
        console.log('Request was made to: ' + req.originalUrl);
        return next();
    });
    
    server.use('/api', apiRoute);
}
module.exports = {
    init: init
};

In the above code, we are doing three things. First one is importing another folder apis which we are going to create next. This folder contains another apis with the version name of our API.

The second one is an anonymous function init. We call this from the app.js file to pass all the routes of the application to this and then return the next callback function.

And the third one is exporting this init function so that we can use it in other files of the application.

Now create a folder apis inside the routes folder and then create two files inside this folder index.js and v1.js. Index.js file will import our different versions of the apis. Keep a different version of our API is always a good practice for the large enterprise level application.

Paste the below code into the index.js file of the apis of routes.

/********
* index.js file (inside routes/apis)
********/

const express = require('express');
const v1ApiController = require('./v1');
let router = express.Router();
router.use('/v1', v1ApiController);
module.exports = router;

And our v1.js file will look like below.


/********
* v1.js file (inside routes/apis)
********/

const userController = require('../../controllers/apis/user');

const express = require('express');
let router = express.Router();
router.use('/users', userController);
module.exports = router;

In the v1.js file of the apis, we are importing the controllers which send our request to the appropriate function and return data from there. Rest of the code in this file is simple.

Create Controllers for the Node.js REST API

Till now, we created our APIs versions and routes, now it is time to create controllers of the API. Create a folder and name it controllers and inside this folder create another folder apis which contains our controllers file of the app.

Inside the apis folder of the controllers, create a file user.js and paste below code into this file.

/********
* user.js file (controllers/apis)
********/


const express = require('express');
const userService = require('../../services/users/user');
let router = express.Router();

router.get('/', userService.getUsers);

router.get('/:id', userService.getUserById);

router.post('/', userService.createUser);

router.put('/:id', userService.updateUser);

router.delete('/:id', userService.deleteUser);

module.exports = router;

This is a very important file of our API. In this file first, we are importing the user services file which we will create next and then routing the all the request depending upon the HTTP methods GET, POST, PUT and DELETE.

Create Services for the Node.js API

We have created our routes and controllers now let’s create a service file for our API. In this tutorial, we are seperating our bussiness logic from the controllers to the services. To design a scalable and robust API, it is a good idea to keep things in proper folders according to their behavior.

Create a services folder and inside this folder create another folder users which contains our user.js service file and return data according to the HTPP methods requested by the client.

Create a user.js file inside the users folder of the services and paste the below code inside this file.

/********
* user.js file (services/users)
********/


const express = require('express');
const User = require('../../models/user');

const getUsers = async (req, res, next) => {
    try {

        let users = await User.find({});

        if (users.length > 0) {
            return res.status(200).json({
                'message': 'users fetched successfully',
                'data': users
            });
        }

        return res.status(404).json({
            'code': 'BAD_REQUEST_ERROR',
            'description': 'No users found in the system'
        });
    } catch (error) {
        return res.status(500).json({
            'code': 'SERVER_ERROR',
            'description': 'something went wrong, Please try again'
        });
    }
}

const getUserById = async (req, res, next) => {
    try {
        let user = await User.findById(req.params.id);
        if (user) {
            return res.status(200).json({
                'message': `user with id ${req.params.id} fetched successfully`,
                'data': user
            });
        }

        return res.status(404).json({
            'code': 'BAD_REQUEST_ERROR',
            'description': 'No users found in the system'
        });

    } catch (error) {

        return res.status(500).json({
            'code': 'SERVER_ERROR',
            'description': 'something went wrong, Please try again'
        });
    }
}

const createUser = async (req, res, next) => {
    try {

        const {
            name,
            email
        } = req.body;

        if (name === undefined || name === '') {
            return res.status(422).json({
                'code': 'REQUIRED_FIELD_MISSING',
                'description': 'name is required',
                'field': 'name'
            });
        }

        if (email === undefined || email === '') {
            return res.status(422).json({
                'code': 'REQUIRED_FIELD_MISSING',
                'description': 'email is required',
                'field': 'email'
            });
        }


        let isEmailExists = await User.findOne({
            "email": email
        });

        if (isEmailExists) {
            return res.status(409).json({
                'code': 'ENTITY_ALREAY_EXISTS',
                'description': 'email already exists',
                'field': 'email'
            });
        }

        const temp = {
            name: name,
            email: email
        }

        let newUser = await User.create(temp);

        if (newUser) {
            return res.status(201).json({
                'message': 'user created successfully',
                'data': newUser
            });
        } else {
            throw new Error('something went worng');
        }
    } catch (error) {
        return res.status(500).json({
            'code': 'SERVER_ERROR',
            'description': 'something went wrong, Please try again'
        });
    }
}

const updateUser = async (req, res, next) => {
    try {


        const userId = req.params.id;

        const {
            name,
            email
        } = req.body;

        if (name === undefined || name === '') {
            return res.status(422).json({
                'code': 'REQUIRED_FIELD_MISSING',
                'description': 'name is required',
                'field': 'name'
            });
        }

        if (email === undefined || email === '') {
            return res.status(422).json({
                'code': 'REQUIRED_FIELD_MISSING',
                'description': 'email is required',
                'field': 'email'
            });
        }


        let isUserExists = await User.findById(userId);

        if (!isUserExists) {
            return res.status(404).json({
                'code': 'BAD_REQUEST_ERROR',
                'description': 'No user found in the system'
            });
        }

        const temp = {
            name: name,
            email: email
        }

        let updateUser = await User.findByIdAndUpdate(userId, temp, {
            new: true
        });

        if (updateUser) {
            return res.status(200).json({
                'message': 'user updated successfully',
                'data': updateUser
            });
        } else {
            throw new Error('something went worng');
        }
    } catch (error) {

        return res.status(500).json({
            'code': 'SERVER_ERROR',
            'description': 'something went wrong, Please try again'
        });
    }
}

const deleteUser = async (req, res, next) => {
    try {
        let user = await User.findByIdAndRemove(req.params.id);
        if (user) {
            return res.status(204).json({
                'message': `user with id ${req.params.id} deleted successfully`
            });
        }

        return res.status(404).json({
            'code': 'BAD_REQUEST_ERROR',
            'description': 'No users found in the system'
        });

    } catch (error) {

        return res.status(500).json({
            'code': 'SERVER_ERROR',
            'description': 'something went wrong, Please try again'
        });
    }
}

module.exports = {
    getUsers: getUsers,
    getUserById: getUserById,
    createUser: createUser,
    updateUser: updateUser,
    deleteUser: deleteUser
}

The user.js service file contains all the required function of the application. In this file, we are importing our model(which we will create next) and then depending upon the HTTP methods, creating a function for them and then exporting it so that we can use it in the controllers file. This file contains below function

  • getUsers: This function will be used to get the details of all the users from the db.
  • getUserById: This function will be used to get the details of all the users from the db by its userID.
  • createUser: This function will be used to create a new user in the db.
  • updateUser: This function will be used to update the user details in the db.
  • updateUser: This function will be used to delete the user details in the db.

Create Models for the API

Till yet, we have created routes, controllers, and services for our API but didn’t create the models of the API which we are using in the services file(user.js) of the API.

Let’s create a models folder and inside that models folder create a file user.js. Our model user.js file will look like below.

/********
* user.js file (models)
********/

let mongoose = require('mongoose');
let Schema = mongoose.Schema;

var User = new Schema({
    name: {
        type: String,
        required : [ true, 'name is required'],
        lowercase : true
    },
    email: {
        type: String,
        required : [ true, 'email is required'],
        unique : true,
        lowercase : true
    }
}, {
    timestamps: true
});

module.exports = mongoose.model('User', User);

In this file, we are importing the mongoose object and defining our schema for the users. For this tutorial, I only define two fields name and email of the users. Email fields of the user are unique by adding a unique property on it. You can learn more about the mongoose schema property from their official website.

Test Node.js REST API

We have built the API and now it is time to test the APIs. You can test the API by using the postman app. If you don’t know what is postman then please go to their website and learn about this awesome app for testing the rest apis.

Let’s start our API by running below command in the terminal and then go to the postman app to test all the URL of the API which we created earlier.

npm start

Get all the users

localhost:3000/api/v1/users
get all user of node.js rest api

Get User by ID

localhost:3000/api/v1/users/5c41e647588715125fbb7ed3
get user by id node.js rest api

Create a User

localhost:3000/api/v1/users
create user in node.js restapi

Update User by Id

localhost:3000/api/v1/users/5c45e1455965992849d7c9e3
update user by id in node.js

Delete User By Id

localhost:3000/api/v1/users/5c45e1455965992849d7c9e3
delete user by id in node.js api

Conclusion

We have created Node.js REST API successfully. This is the only basic of the Nodejs REST API. There are still lots of things to do in this API like adding authentications and authorizations in the API, logging in the application, monitoring of the API, global error handling in the API and lots of more things.

But by following this tutorial, now you are able to create scalable Node.js REST API for your next project. If you want to get the full source code for this then you can get it from my Github link.

If you have any doubt or suggestion regarding this tutorial then comment below.

Codecademy Web Development
Pin It