DEV Community

Chiranjib
Chiranjib

Posted on

Integrate Node.js backend with MongoDB

Previous: Create modular routes with express

Now that we have set up a Node.js backend with express.js and understand how we want to define our controllers and entities, it's time to make our setup more realistic. Our backend right now is missing a very crucial aspect - persistence (or in simple words, it needs a database).

Staying true to our stack, let's integrate MongoDB. At this point, we should download and install MongoDB server. You may use this link: MongoDB Community Server

To understand things fully, we have to come up with an example. Here's what we are going with:

Our application maintains a database of movies, and their ratings by people. So, we are dealing with three entities: User (you or me), Movie (the things that require our time, money and attention) and Rating (link between User & Movie).

Step 1 - get mongoose

MongoDB is a schemaless database, which means you may store any JSON in any collection and a new onlooker can potentially be left absolutely mesmerized.
Enter mongoose - a library that provides Schemas and Models to make dealing with data on MongoDB a little more predictable.

Let's navigate to our Node.js project root, and execute this

npm i --save mongoose

Our package.json should have this new entry now:

    "dependencies": {
        "express": "4.18.2",
        "helmet": "6.0.1",
        "link-module-alias": "1.2.0",
        "mongoose": "6.9.2",
        "winston": "3.8.2"
    }
Enter fullscreen mode Exit fullscreen mode

Step 2 - setting up for data-access

Going back to the drawing board a little bit, let us establish a data-access module (folder) within our project.

Create a data-access folder
mkdir ./data-access

Data-access is again something that you are probably going to need throughout your application code, even in nested places. Let's set it up for access with absolute path. Add an entry in the _moduleAliases section of package.json:

    "_moduleAliases": {
        "_controllers": "./controllers",
        "_entities": "./entities",
        "_utils": "./utils",
        "_config": "./config",
        "_data-access": "./data-access"
    }
Enter fullscreen mode Exit fullscreen mode

Once done, we have to create the linkage of the new directory by executing
npm i

Notice in the console output:

> link-module-alias

link-module-alias: _controllers -> ./controllers, _entities -> ./entities, _utils -> ./utils, _config -> ./config, _data-access -> ./data-access
Enter fullscreen mode Exit fullscreen mode

Step 3 - time to light it up

Let's understand something trivial - the application will be in an impaired state if the database connection doesn't work. So, before anything else, our Node.js process should connect to the database and then start the app.

Assuming that we are working with a local copy of MongoDB, let's modify the ./config/index.js file to include database details:

...
    DATABASE: {
        URL: 'mongodb://127.0.0.1',
        DB_NAME: 'moviemania'
    }
Enter fullscreen mode Exit fullscreen mode

Our sample application is very simplistic, but real world applications may work with multiple databases and multiple models. With that in consideration, it is a good practice to set up a data connection factory. Our implementation makes use of the Factory Design Pattern and the Singleton Design Pattern. Create a file ./data-access/ConnectionFactory.js as:

const { DATABASE: { URL, DB_NAME } } = require('_config');
const mongoose = require('mongoose');
/**
 * @type {mongoose.Connection}
 */
let dbConnection;

module.exports = {
    getDBConnection: function () {
        if (!dbConnection) {
            dbConnection = mongoose.createConnection(URL, { dbName: DB_NAME, maxPoolSize: 10 });
        }
        return dbConnection;
    },
    closeConnection: function () {
        if (dbConnection && dbConnection.readyState === 1) {
            dbConnection.close();
        }
    }
};

Enter fullscreen mode Exit fullscreen mode

We will have to refactor our index.js as:

  • one function to connect to the database
  • another subsequent function that starts the app
const logger = require('_utils/logger');

function connectToDB() {
    const { getDBConnection } = require('_data-access/ConnectionFactory');
    const connection = getDBConnection();
    return new Promise((resolve, reject) => {
        let counter = 1;
        const timer = setInterval(function () {
            logger.info(`[${counter}] Checking connection..`);
            if (connection.readyState === 1) {
                clearInterval(timer);
                resolve();
            } else if (counter === 5) {
                clearInterval(timer);
                reject('Could not connect to database after 5 retries');
            }
            counter++;
        }, 1000);
    });
}

function setupApp() {
    const {
        SERVER: { PORT, REQUEST_BODY_SIZE_LIMIT },
    } = require('_config');
    const { getController } = require('_controllers');
    const helmet = require('helmet');
    const express = require('express');
    const entities = require('_entities');
    const app = express();

    app.use((req, res, next) => {
        logger.log('info', `Received request [${req.method}] ${req.originalUrl}`);
        next();
    });

    app.use(helmet());
    app.use(express.json({ limit: REQUEST_BODY_SIZE_LIMIT }));
    app.use(express.urlencoded({ extended: true, limit: REQUEST_BODY_SIZE_LIMIT }));

    app.get('/healthcheck', (req, res, next) => {
        res.send('OK');
    });

    app.use(...getController(entities.getEntityOne()));
    const logger = require('_utils/logger');

function connectToDB() {
    const { getDBConnection } = require('_data-access/ConnectionFactory');
    const connection = getDBConnection();
    return new Promise((resolve, reject) => {
        let counter = 1;
        const timer = setInterval(function () {
            logger.info(`[${counter}] Checking connection..`);
            if (connection.readyState === 1) {
                clearInterval(timer);
                resolve();
            } else if (counter === 5) {
                clearInterval(timer);
                reject('Could not connect to database after 5 retries');
            }
        }, 1000);
    });
}

function setupApp() {
    const {
        SERVER: { PORT, REQUEST_BODY_SIZE_LIMIT },
    } = require('_config');
    const { getController } = require('_controllers');
    const helmet = require('helmet');
    const express = require('express');
    const entities = require('_entities');
    const app = express();

    app.use((req, res, next) => {
        logger.log('info', `Received request [${req.method}] ${req.originalUrl}`);
        next();
    });

    app.use(helmet());
    app.use(express.json({ limit: REQUEST_BODY_SIZE_LIMIT }));
    app.use(express.urlencoded({ extended: true, limit: REQUEST_BODY_SIZE_LIMIT }));

    app.get('/healthcheck', (req, res, next) => {
        res.send('OK');
    });

    app.use(...getController(entities.getEntityOne()));
    app.use('/', (req, res) => {
        res.send(`${req.originalUrl} can not be served`);
    });

    app.listen(PORT, () => {
        logger.log('info', `Listening on port ${PORT}`);
    });
}

connectToDB()
    .then(function () {
        logger.info('Connected to database, starting app now...');
        setupApp();
    })
    .catch(function (e) {
        logger.error('Failed to connect to database.', e);
    });
    app.use(...getController(entities.getUserEntity()));

    app.use('/', (req, res) => {
        res.send(`${req.originalUrl} can not be served`);
    });

    app.listen(PORT, () => {
        logger.log('info', `Listening on port ${PORT}`);
    });
}

connectToDB()
    .then(function () {
        logger.info('Connected to database, starting app now...');
        setupApp();
    })
    .catch(function (e) {
        logger.error('Failed to connect to database.', e);
    });

Enter fullscreen mode Exit fullscreen mode

If you notice carefully, our connectToDB function returns a Promise. Only when the Promise is fulfilled would the next function setupApp be invoked.

So, we have set up a Node.js backend and connected to MongoDB. Next up, we will connect the dots between Controllers, Entities and Models.

Next: Working with Node.js Entities and Mongoose Models - I

Top comments (0)