Integrate Node.js backend with MongoDB
Tags: express, node, mongodb, mongoose
Published: 2023-02-19 13:09:43

**<u>_Previous:_</u>** [Create modular routes with express](https://dev.to/chiranjib_b/create-modular-routes-with-express-5cp9)

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](https://www.mongodb.com/try/download/community)

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](https://www.npmjs.com/package/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"
    }
```

####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"
    }
```

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
```

####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'
    }
```

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();
        }
    }
};

```

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);
    });

```

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.

**<u>_Next:_</u>** [Working with Node.js Entities and Mongoose Models - I](https://dev.to/chiranjib_b/working-with-nodejs-entities-and-mongoose-models-i-1p05)