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"
}
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.connect(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');
return new Promise((resolve, reject) => {
getDBConnection()
.then(connection => {
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);
})
.catch(e => {
logger.error(e)
reject('Could not connect to database')
});
});
}
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) => {
res.send('OK');
});
app.use(...getController(entities.getEntityOne()));
app.use(...getController(entities.getMovieEntity()));
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.
Top comments (0)