Another ExpressJS API tutorial for 2021, part 10— MongoDB!
Hey there, in this article we are going to use the advantage of our previously Docker configuration to add a MongoDB to our API made with ExpressJS and Typescript!

Hi there, if it is your first time arriving here we are creating a series on how to build an API using ExpressJS and Typescript. The first article can be found here.
Just as a quick overview, here is the series of articles that we are writing:
- Create your hello world ExpressJS API (Yes… we always need to start with a hello world)
- Configure our hello world application to use Typescript (TS is cool, believe me ;) )
- Create our first CRUD API endpoints for an user (Everyone wants a registered user in their applications, no?)
- Create and configure your controllers (We will talk about it after)
- Create and configure your services (What the h. is that?)
- Middleware’s! (Yes, we need and use it everyday)
- End to end testings!
- Configuring a secure way to manage user permissions via API
- Putting all together into a Docker container
- Configuring a simple MongoDB to connect to our application — We are here now ;)
- Configuring permission level to the application
- Add logs with Winston!
Welcome back again, so in this article we are going to remove our handmade in memory database to add a real database that in this case it will be a MongoDB.
How should we start?
First, make sure to be using the latest version of our project that can be found here.
Now, let’s update our docker-composer.yml file:
version: '3'
services:
api:
command: npm run test-dev
build: .
volumes:
- ./api:/app
links:
- mongo
networks:
- backend
ports:
- "3000:3000"mongo:
image: mongo
volumes:
- ./data:/data/db
networks:
- backend
ports:
- "27017:27017"networks:
backend:
driver: bridgeThanks to the Docker, with a few lines of code we have our MongoDB ready to use. Couldn’t be that easy right? Since we are using a Docker containers, the MongoDB will be used via container and you wouldn’t need to do any extra steps to have it ready to use at your application. Now, let’s go configure it at our ExpressJS API!
First of all, make sure that you have installed all the required dependencies, so please update your package.json and run npm install
{
"name": "expressjs-api-tutorial",
"version": "0.0.1",
"description": "Tutorial of how to create a REST API using ExpressJS",
"main": "dist/app.js",
"scripts": {
"tsc": "tsc",
"dev-debug": "nodemon --watch . --ext ts --exec \"npm run tsc\"",
"start": "npm run tsc && node ./dist/app.js",
"test-dev": "nodemon --watch . --ext ts --exec \"mocha -r ts-node/register test/**/*.test.ts\"",
"dev": "nodemon --watch . --ext ts --exec \"docker-compose build && docker-compose up\"",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/makinhs/expressjs-api-tutorial.git"
},
"keywords": [
"REST",
"API",
"ExpressJS",
"NodeJS"
],
"author": "Marcos Silva",
"license": "ISC",
"bugs": {
"url": "https://github.com/makinhs/expressjs-api-tutorial/issues"
},
"homepage": "https://github.com/makinhs/expressjs-api-tutorial#readme",
"dependencies": {
"argon2-pass": "^1.0.2",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.8.11",
"short-uuid": "^3.1.1"
},
"devDependencies": {
"@types/chai": "^4.2.8",
"@types/express": "^4.17.2",
"@types/mocha": "^7.0.1",
"@types/mongoose": "^5.7.0",
"@types/node": "^13.5.2",
"@types/supertest": "^2.0.8",
"chai": "^4.2.0",
"mocha": "^7.0.1",
"nodemon": "^2.0.2",
"source-map-support": "^0.5.16",
"supertest": "^4.0.2",
"ts-node": "^8.6.2",
"tslint": "^6.0.0",
"typescript": "^3.7.5"
}
}We will create a file to control our mongoose integration. At the app/common/services folder let’s create a file called mongoose.service.ts and add the following:
import mongoose from 'mongoose';export class MongooseService {
private static instance: MongooseService;options = {
autoIndex: false,
poolSize: 10,
bufferMaxEntries: 0,
useNewUrlParser: true,
useUnifiedTopology: true
};
count = 0;constructor() {
this.connectWithRetry();
}public static getInstance() {
if (!this.instance) {
this.instance = new MongooseService();
}
return this.instance;
}getMongoose(){
return mongoose;
}connectWithRetry() {
console.log('MongoDB connection with retry');
mongoose.connect("mongodb://mongo:27017/api-db", this.options).then(() => {
console.log('MongoDB is connected')
}).catch(err => {
console.log('MongoDB connection unsuccessful, retry after 5 seconds. ', ++this.count);
setTimeout(this.connectWithRetry, 5000)
})
};}
We are going to use this mongoose service to get the Mongoose to be used in our application, and also in a case of need any changes there we will only need to change one file instead of multiple ones.
Now, at our app/users/daos folder, let’s create a file called users.dao.ts with the following:
import {MongooseService} from '../../common/services/mongoose.service';
import * as shortUUID from "short-uuid";export class UsersDao {
mongooseService: MongooseService = MongooseService.getInstance();
private static instance: UsersDao;Schema = this.mongooseService.getMongoose().Schema;userSchema = new this.Schema({
_id: String,
name: String,
email: String,
description: String,
password: String,
permissionLevel: Number
});User = this.mongooseService.getMongoose().model('Users', this.userSchema);constructor() {
}public static getInstance() {
if (!this.instance) {
this.instance = new UsersDao();
}
return this.instance;
}async addUser(userFields: any) {
userFields._id = shortUUID.generate();
const user = new this.User(userFields);
await user.save();
return userFields._id;
}async getUserByEmail(email: string) {
return this.User.findOne({email: email});
}async removeUserById(userId: string) {
await this.User.deleteOne({_id: userId});
}async getUserById(userId: string) {
return this.User.findOne({_id: userId});
}async listUsers(limit: number = 25, page: number = 0) {
return this.User.find()
.limit(limit)
.skip(limit * page)
.exec();
}async patchUser(userFields: any) {
let user: any = await this.User.findById(userFields._id);
if(user){
for (let i in userFields) {
user[i] = userFields[i];
}
return await user.save();
}
}
}This file represents our integration with MongoDB using Mongoose for our CRUD users resources. We are using basic Mongoose integration to make the CRUD to work, but please remember, as always, to read the documentation to understand it better and to use wisely at your own project. Also, you can find a good introduction of Mongoose here.
What we need to do now, is to change our users service to use our new file which will communicate with our MongoDB.
Open now your /app/users/services/user.services.ts file and update it to call our new file and methods.
import {CRUD} from '../../common/interfaces/crud.interface';
import {GenericInMemoryDao} from '../daos/in.memory.dao';
import {UsersDao} from '../daos/users.dao';
export class UsersService implements CRUD {
private static instance: UsersService;
dao: GenericInMemoryDao;constructor() {
this.dao = GenericInMemoryDao.getInstance();
}static getInstance(): UsersService {
if (!UsersService.instance) {
UsersService.instance = new UsersService();
}
return UsersService.instance;
}create(resource: any) {
return UsersDao.getInstance().addUser(resource);
}deleteById(resourceId: any) {
return UsersDao.getInstance().removeUserById(resourceId);
};list(limit: number, page: number) {
return UsersDao.getInstance().listUsers(limit, page);
};patchById(resource: any) {
return UsersDao.getInstance().patchUser(resource);
};readById(resourceId: any) {
return UsersDao.getInstance().getUserById(resourceId);
};updateById(resource: any) {
return UsersDao.getInstance().patchUser(resource);
};async getByEmail(email: string) {
return UsersDao.getInstance().getUserByEmail(email);
}
}Note that we are importing a UsersDao now and using it at our functions.
import {UsersDao} from '../daos/users.dao';
...return UsersDao.getInstance().getUserByEmail(email);
...And that’s it, every other piece of the code that will call the user service should not be changed.
Having a service that calls your DAO integration will make it easier for a future change of database and even a code maintenance
Now, let’s just run npm run dev and see our code working. You can stop and re-run it and you will see that our data is intact ;) That’s it, MongoDB up and running for us.
In this article we configured a MongoDB using the benefits of the Docker Containers and our architecture of code to make it as a smooth switch from a in-memory dao to the MongoDB.
Thanks for reading and see you at the next article!
tips:
- the full code of this article is here
- don’t forget to use a nice app to test your API such as Postman or Insomnia
- I know you still didn’t… Read the ExpressJS documentation!
- Learn Docker, start at their Documentation!
- Learn Mongoose, start also at their Documentation!
- Complete project is here! (Includes even the non existing articles yet)
