Published:
Last updated:

NodeJS + MongoDB - Simple API for Authentication, Registration and User Management

Tutorial built with Node.js and MongoDB

Other versions available:

In this tutorial we'll go through an example Node.js + MongoDB API that supports user registration, login with JWT authentication and user management. For an extended example that includes email verification, role based authorization and forgot password functionality see Node + Mongo - Boilerplate API with Email Sign Up, Verification, Authentication & Forgot Password.

After building a few front end tutorials and examples on user authentication with Angular and React I thought I'd put together a simple custom backend api that can be easily 'plugged in' to the front ends, or be easily hooked up with your own custom client application.

The api is written in JavaScript for NodeJS and requires MongoDB to be running. Mongoose is used to connect to MongoDB, define the database schema and read/write data. Express is used as the web server.

The project is available on GitHub at https://github.com/cornflourblue/node-mongo-registration-login-api.

Update History:

  • 02 Jul 2020 - Updated to express-jwt version 6.0.0 to fix security vulnerability
  • 27 Apr 2020 - Updated Mongoose model and added instructions for testing with Postman and deploying to AWS
  • 14 Jun 2018 - Built with Node.js and MongoDB


Node + Mongo Tutorial Contents


Running the NodeJS + MongoDB API Locally

  1. Install NodeJS and NPM from  https://nodejs.org/en/download/.
  2. Install MongoDB Community Server from  https://www.mongodb.com/download-center.
  3. Run MongoDB, instructions are available on the install page for each OS at https://docs.mongodb.com/manual/administration/install-community/
  4. Download or clone the project source code from https://github.com/cornflourblue/node-mongo-registration-login-api
  5. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  6. Start the api by running npm start from the command line in the project root folder, you should see the message Server listening on port 4000. Follow the instructions below to test with Postman or hook up with one of the example single page applications available (React, Angular or Vue).


Testing the Node Auth API with Postman

Postman is a great tool for testing APIs, you can download it at https://www.getpostman.com/.

Below are instructions on how to use Postman to register a new user with the api, authenticate a user to get a JWT token, and then make an authenticated request with the JWT token to retrieve a list of users from the api.

How to register a new user with Postman

To register a new user with the api follow these steps:

  1. Open a new request tab by clicking the plus (+) button at the end of the tabs.
  2. Change the http request method to "POST" with the dropdown selector on the left of the URL input field.
  3. In the URL field enter the address to the register route of your local API - http://localhost:4000/users/register.
  4. Select the "Body" tab below the URL field, change the body type radio button to "raw", and change the format dropdown selector to "JSON (application/json)".
  5. Enter a JSON object containing the required user properties in the "Body" textarea, e.g:
    {
        "firstName": "Jason",
        "lastName": "Watmore",
        "username": "jason",
        "password": "my-super-secret-password"
    }
  6. Click the "Send" button, you should receive a "200 OK" response with an empty JSON object in the response body.

Here's a screenshot of Postman after the request is sent and the new user has been registered:


How to authenticate a user with Postman

To authenticate a user with the api and get a JWT token follow these steps:

  1. Open a new request tab by clicking the plus (+) button at the end of the tabs.
  2. Change the http request method to "POST" with the dropdown selector on the left of the URL input field.
  3. In the URL field enter the address to the authenticate route of your local API - http://localhost:4000/users/authenticate.
  4. Select the "Body" tab below the URL field, change the body type radio button to "raw", and change the format dropdown selector to "JSON (application/json)".
  5. Enter a JSON object containing the username and password in the "Body" textarea:
    {
        "username": "jason",
        "password": "my-super-secret-password"
    }
  6. Click the "Send" button, you should receive a "200 OK" response with the user details including a JWT token in the response body, make a copy of the token value because we'll be using it in the next step to make an authenticated request.

Here's a screenshot of Postman after the request is sent and the user has been authenticated:


How to make an authenticated request to retrieve all users

To make an authenticated request using the JWT token from the previous step, follow these steps:

  1. Open a new request tab by clicking the plus (+) button at the end of the tabs.
  2. Change the http request method to "GET" with the dropdown selector on the left of the URL input field.
  3. In the URL field enter the address to the users route of your local API - http://localhost:4000/users.
  4. Select the "Authorization" tab below the URL field, change the type to "Bearer Token" in the type dropdown selector, and paste the JWT token from the previous authenticate step into the "Token" field.
  5. Click the "Send" button, you should receive a "200 OK" response containing a JSON array with all the user records in the system.

Here's a screenshot of Postman after making an authenticated request to get all users:


How to update a user with Postman

To update a user with the api follow these steps:

  1. Open a new request tab by clicking the plus (+) button at the end of the tabs.
  2. Change the http request method to "PUT" with the dropdown selector on the left of the URL input field.
  3. In the URL field enter the address to the /users/{id} route with the id of the user you registered above, e.g - http://localhost:4000/users/1.
  4. Select the "Authorization" tab below the URL field, change the type to "Bearer Token" in the type dropdown selector, and paste the JWT token from the previous authenticate step into the "Token" field.
  5. Select the "Body" tab below the URL field, change the body type radio button to "raw", and change the format dropdown selector to "JSON (application/json)".
  6. Enter a JSON object in the "Body" textarea containing the properties you want to update, for example to update the first and last names:
    {
        "firstName": "Foo",
        "lastName": "Bar"
    }
  7. Click the "Send" button, you should receive a "200 OK" response with an empty JSON object in the response body.

Here's a screenshot of Postman after the request is sent and the user has been updated:


Running a React client application with the NodeJS API

For full details about the example React application see the post React Hooks + Redux - User Registration and Login Tutorial & Example. But to get up and running quickly just follow the below steps.

  1. Download or clone the React tutorial code from https://github.com/cornflourblue/react-hooks-redux-registration-login-example
  2. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  3. Remove or comment out the 2 lines below the comment // setup fake backend located in the /src/index.jsx file.
  4. Start the application by running npm start from the command line in the project root folder, this will launch a browser displaying the application and it should be hooked up with the NodeJS + MongoDB API that you already have running.


Deploy API to AWS with a React front end

This video shows how to setup a production ready web server from scratch on AWS and deploy the Node.js + MongoDB API with a React + Redux client app. The tutorial used in the video is available at React + Node.js on AWS - How to Deploy a MERN Stack App to Amazon EC2.


Running an Angular 8 client application with the NodeJS API

For full details about the example Angular 8 application see the post Angular 8 - User Registration and Login Example & Tutorial. But to get up and running quickly just follow the below steps.

  1. Download or clone the Angular 8 tutorial code from https://github.com/cornflourblue/angular-8-registration-login-example
  2. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  3. Remove or comment out the line below the comment // provider used to create fake backend located in the /src/app/app.module.ts file.
  4. Start the application by running npm start from the command line in the project root folder, this will launch a browser displaying the application and it should be hooked up with the NodeJS + MongoDB API that you already have running.


Deploy API to AWS with an Angular front end

This video shows how to setup a production ready web server from scratch on AWS and deploy the Node.js + MongoDB API with an Angular client app. The tutorial used in the video is available at Angular + Node.js on AWS - How to Deploy a MEAN Stack App to Amazon EC2.


Running a Vue client application with the NodeJS API

For full details about the example Vue application see the post Vue + Vuex - User Registration and Login Tutorial & Example. But to get up and running quickly just follow the below steps.

  1. Download or clone the Vue tutorial code from https://github.com/cornflourblue/vue-vuex-registration-login-example
  2. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  3. Remove or comment out the 2 lines below the comment // setup fake backend located in the /src/index.js file.
  4. Start the application by running npm start from the command line in the project root folder, this will launch a browser displaying the application and it should be hooked up with the NodeJS + MongoDB API that you already have running.


Deploy API to AWS with a Vue front end

This video shows how to setup a production ready web server from scratch on AWS and deploy the Node.js + MongoDB API with a Vue client app. The tutorial used in the video is available at Vue.js + Node.js on AWS - How to Deploy a MEVN Stack App to Amazon EC2.


Deploying the API to Heroku

For details on how to deploy the api to the Heroku Cloud see the post Deploy to Heroku - Node + Mongo API for Authentication, Registration and User Management.

It includes detailed instructions for deploying to Heroku with the Heroku CLI and instructions on testing with Postman.


NodeJS Authentication API Project Structure

The project is structured into "feature folders" (users) "non-feature / shared component folders" (_helpers). Shared component folders contain code that can be used by multiple features and other parts of the application, and are prefixed with an underscore to group them together so it's easier to see what's what at a glance.

The example only contains the single users feature, but this can be easily extended to handle any other feature by copying the users folder and following the same pattern.

 

NodeJS Helpers Folder

Path: /_helpers

The helpers folder contains all the bits and pieces that don't fit into other folders but don't justify having a folder of their own.

 

NodeJS MongoDB Database Wrapper

Path: /_helpers/db.js

The MongoDB wrapper connects to MongoDB using Mongoose and exports an object containing all of the database model objects in the application (currently only User). It provides an easy way to access any part of the database from a single point.

const config = require('config.json');
const mongoose = require('mongoose');
const connectionOptions = { useCreateIndex: true, useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false };
mongoose.connect(process.env.MONGODB_URI || config.connectionString, connectionOptions);
mongoose.Promise = global.Promise;

module.exports = {
    User: require('../users/user.model')
};
 

NodeJS Global Error Handler Middleware

Path: /_helpers/error-handler.js

The global error handler is used catch all errors and remove the need for redundant error handler code throughout the application. It's configured as middleware in the main server.js file.

module.exports = errorHandler;

function errorHandler(err, req, res, next) {
    if (typeof (err) === 'string') {
        // custom application error
        return res.status(400).json({ message: err });
    }

    if (err.name === 'ValidationError') {
        // mongoose validation error
        return res.status(400).json({ message: err.message });
    }

    if (err.name === 'UnauthorizedError') {
        // jwt authentication error
        return res.status(401).json({ message: 'Invalid Token' });
    }

    // default to 500 server error
    return res.status(500).json({ message: err.message });
}
 

NodeJS JWT Middleware

Path: /_helpers/jwt.js

The node JWT middleware checks that the JWT token received in the http request from the client is valid before allowing access to the API, if the token is invalid a "401 Unauthorized" response is sent to the client.

JWT authentication is used on all routes except for the authenticate and register routes which are public.

const expressJwt = require('express-jwt');
const config = require('config.json');
const userService = require('../users/user.service');

module.exports = jwt;

function jwt() {
    const secret = config.secret;
    return expressJwt({ secret, algorithms: ['HS256'], isRevoked }).unless({
        path: [
            // public routes that don't require authentication
            '/users/authenticate',
            '/users/register'
        ]
    });
}

async function isRevoked(req, payload, done) {
    const user = await userService.getById(payload.sub);

    // revoke token if user no longer exists
    if (!user) {
        return done(null, true);
    }

    done();
};
 

NodeJS Users Folder

Path: /users

The users folder contains all code that is specific to the users feature of the api.

 

NodeJS Mongoose User Model

Path: /users/user.model.js

The user model uses Mongoose to define the schema for the users collection saved in MongoDB. The exported Mongoose model object gives full access to perform CRUD (create, read, update, delete) operations on users in MongoDB, see the user service below for examples.

schema.set('toJSON', { ... }); configures which user properties are included when converting MongoDB records to JSON objects which are returned in API responses.

  • virtuals: true includes the Mongoose virtual id property which is a copy of the MongoDB _id property.
  • versionKey: false excludes the Mongoose version key (__v).
  • transform: function (doc, ret) { ... } removes the MongoDB _id property and password hash so they are not included in API responses.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const schema = new Schema({
    username: { type: String, unique: true, required: true },
    hash: { type: String, required: true },
    firstName: { type: String, required: true },
    lastName: { type: String, required: true },
    createdDate: { type: Date, default: Date.now }
});

schema.set('toJSON', {
    virtuals: true,
    versionKey: false,
    transform: function (doc, ret) {
        delete ret._id;
        delete ret.hash;
    }
});

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

NodeJS User Service

Path: /users/user.service.js

The user service contains the core business logic for user authentication and management in the node api, it encapsulates all interaction with the mongoose user model and exposes a simple set of methods which are used by the users controller below.

The top of the file contains the service method definitions so it's easy to see all methods at a glance, the rest of the file contains the method implementations.

const config = require('config.json');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const db = require('_helpers/db');
const User = db.User;

module.exports = {
    authenticate,
    getAll,
    getById,
    create,
    update,
    delete: _delete
};

async function authenticate({ username, password }) {
    const user = await User.findOne({ username });
    if (user && bcrypt.compareSync(password, user.hash)) {
        const token = jwt.sign({ sub: user.id }, config.secret, { expiresIn: '7d' });
        return {
            ...user.toJSON(),
            token
        };
    }
}

async function getAll() {
    return await User.find();
}

async function getById(id) {
    return await User.findById(id);
}

async function create(userParam) {
    // validate
    if (await User.findOne({ username: userParam.username })) {
        throw 'Username "' + userParam.username + '" is already taken';
    }

    const user = new User(userParam);

    // hash password
    if (userParam.password) {
        user.hash = bcrypt.hashSync(userParam.password, 10);
    }

    // save user
    await user.save();
}

async function update(id, userParam) {
    const user = await User.findById(id);

    // validate
    if (!user) throw 'User not found';
    if (user.username !== userParam.username && await User.findOne({ username: userParam.username })) {
        throw 'Username "' + userParam.username + '" is already taken';
    }

    // hash password if it was entered
    if (userParam.password) {
        userParam.hash = bcrypt.hashSync(userParam.password, 10);
    }

    // copy userParam properties to user
    Object.assign(user, userParam);

    await user.save();
}

async function _delete(id) {
    await User.findByIdAndRemove(id);
}
 

NodeJS Express Users Controller

Path: /users/users.controller.js

The users controller defines all user routes for the api, the route definitions are grouped together at the top of the file and the implementations are below.

Express is the web server used by the api, it's one of the most popular web application frameworks for NodeJS.

const express = require('express');
const router = express.Router();
const userService = require('./user.service');

// routes
router.post('/authenticate', authenticate);
router.post('/register', register);
router.get('/', getAll);
router.get('/current', getCurrent);
router.get('/:id', getById);
router.put('/:id', update);
router.delete('/:id', _delete);

module.exports = router;

function authenticate(req, res, next) {
    userService.authenticate(req.body)
        .then(user => user ? res.json(user) : res.status(400).json({ message: 'Username or password is incorrect' }))
        .catch(err => next(err));
}

function register(req, res, next) {
    userService.create(req.body)
        .then(() => res.json({}))
        .catch(err => next(err));
}

function getAll(req, res, next) {
    userService.getAll()
        .then(users => res.json(users))
        .catch(err => next(err));
}

function getCurrent(req, res, next) {
    userService.getById(req.user.sub)
        .then(user => user ? res.json(user) : res.sendStatus(404))
        .catch(err => next(err));
}

function getById(req, res, next) {
    userService.getById(req.params.id)
        .then(user => user ? res.json(user) : res.sendStatus(404))
        .catch(err => next(err));
}

function update(req, res, next) {
    userService.update(req.params.id, req.body)
        .then(() => res.json({}))
        .catch(err => next(err));
}

function _delete(req, res, next) {
    userService.delete(req.params.id)
        .then(() => res.json({}))
        .catch(err => next(err));
}
 

NodeJS App Config

Path: /config.json

The app config file contains configuration data for the api.

IMPORTANT: The "secret" property is used by the api to sign and verify JWT tokens for authentication, update it with your own random string to ensure nobody else can generate a JWT to gain unauthorised access to your application.

{
    "connectionString": "mongodb://localhost/node-mongo-registration-login-api",
    "secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
}
 

NodeJS Main Server Entrypoint

Path: /server.js

The server.js file is the entry point into the api, it configures application middleware, binds controllers to routes and starts the Express web server for the api.

require('rootpath')();
const express = require('express');
const app = express();
const cors = require('cors');
const bodyParser = require('body-parser');
const jwt = require('_helpers/jwt');
const errorHandler = require('_helpers/error-handler');

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cors());

// use JWT auth to secure the api
app.use(jwt());

// api routes
app.use('/users', require('./users/users.controller'));

// global error handler
app.use(errorHandler);

// start server
const port = process.env.NODE_ENV === 'production' ? (process.env.PORT || 80) : 4000;
const server = app.listen(port, function () {
    console.log('Server listening on port ' + port);
});

 

Subscribe or Follow Me For Updates

Subscribe to my YouTube channel or follow me on Twitter or GitHub to be notified when I post new content.

 


Supported by