“Experts often possess more data than judgment.”
― Colin Powell
Introduction
In the previous post, we learned about creating a new Node.js API project and setting up the necessary libraries. We created the endpoints, tested it on Postman, and got the dummy results successfully.
Let’s make our API more useful. In this second post, we will learn how to create, read, update, and delete data from MongoDB. And then, we will integrate the API from the previous post with it. And like the previous post, I will also share the complete project on GitHub at the end of the post.
Requirements
MongoDB
“MongoDB is a cross-platform document-oriented database program. Classified as a NoSQL database program, MongoDB uses JSON-like documents with optional schemas. MongoDB is developed by MongoDB Inc. and licensed under the Server Side Public License.”
Wikipedia
You can find the installation steps here.
MongoDB GUI
If you would like to browse your database with a GUI, you can download NoSQL Booster or Robo3T.
Part 2 – Read, Insert, Update, Delete Data Using MongoDB
Prerequisites
Start Fresh (if you want to)
If you’d like to start fresh to learn this topic, please clone from this repository in GitHub.
Install MongoDB Library
In the project root, run this command: npm i mongodb
. You should see something like this screenshot below if everything goes smoothly:

Make sure MongoDB service is running
Open your terminal, then execute mongo
. If it shows like the screenshot below, then it means the MongoDB is running well.

You can also put MongoDB query to modify the database. But, personally I prefer using the NoSQL Booster to check them out though.
Connecting to MongoDB
We will create a script named mongoDbConnector.js
. This script will contain everything related to MongoDB.
Create a mongodb connector module
First, we have to connect to the service to use its capabilities. See this snippet below on mongoDbConnector.js
:
// mongoDbConnector.js
const mongodb = require('mongodb');
class MongoDbConnector {
constructor(config){
Object.assign(this, { config });
}
connect(){
const { host, name } = this.config;
const options = {
useUnifiedTopology: true,
useNewUrlParser: true
};
mongodb.MongoClient.connect(host, options,
(err, client) => {
if (err) {
console.log(err);
}
else {
console.log("Connected successfully to DB");
const db = client.db(name);
Object.assign(this, { db, client });
}
});
}
disconnect(){
this.client.close();
console.log("Disconnected DB successfully!");
}
}
module.exports = MongoDbConnector;
First we create a MongoDbConnector
class, where all MongoDB operations will be executed. Then, we create to public functions, connect()
and disconnect()
. We will use connect()
when starting the API service, and disconnect()
when the service is killed.
In connect()
, we use the MongoClient.connect()
from the mongodb library. The function needs some arguments: host, options, and a callback function. In the callback function, if it connects successfully, we use client.db(name)
to use the database we want. Finally, we assign db
and client
as properties of the MongoDbConnector object.
Then, in disconnect()
, we want to close the connection to the database server. Executing this.client.close()
will terminate the connection.
Lastly, export the class as a module using module.exports
, because we will use it from index.js
.
IMPORTING mongodb connector
OK, we have now set MongoDbConnector
class. Now, let’s go back to index.js
, where we will import the MongoDbConnector
class and create an object from it. See this snippet:
// index.js
...
const port = 3000;
const MongoDbConnector = require('./mongoDbConnector');
const mongoDbConnector = new MongoDbConnector({
name: 'cil-rest-api',
host: 'mongodb://localhost:27017'
});
mongoDbConnector.connect();
app.use(bodyParser.json());
app.post('/user', (req, res) => { ... });
...
app.listen(port, () => {...});
['SIGINT', 'SIGTERM'].forEach((signal) => {
process.on(signal, async () => {
console.log("Stop signal received");
mongoDbConnector.disconnect();
console.log("Exiting now, bye!");
process.exit(0);
});
});
First, we import the class: require('./mongoDbConnector')
.
Then, we create a MongoDbConnector
object by also passing the configuration object as the argument: const mongoDbConnector = new MongoDbConnector(config);
The configuration properties are only the database name
and host
for now.
Lastly, we call mongoDbConnector.connect()
to connect to the database server.
Where do we use .disconnect()
? See the bottom part of the snippet. The function will be called if we decide to stop the API service through SIGINT
or SIGTERM
. Just before process.exit(0)
is executed, we must close all connections to the remote server cleanly.
Test the code
If everything is done correctly, you should see the result pretty much like this picture below. Start the app with npm start
in the terminal. To execute the stop signal, type Ctrl+C.

Create New User
mongodb connector
Let’s get back to mongoDbConnector.js
. We will add the code to save the new users in the database. See this snippet below:
// mongoDbConnector.js
...
class MongoDbConnector {
...
disconnect() { ... }
async insertOne(collection, data){
try {
const res = await this.db.collection(collection)
.insertOne(data);
const textRes = res.result.ok ?
`Success insert data to collection ${collection}!`:
`Failed insert data to collection ${collection}!`;
console.log(textRes);
return textRes;
}
catch(err) {
throw err;
}
}
}
The insertOne
function is an asynchronous function, because the results will be returned longer since it is on a different service. This function requires the parameters collection
and data
. Notice that there is no need to specify creating a new collection in MongoDB, because MongoDB automatically creates a new collection if it does not exist.
Because this operation depends on the status of MongoDB service, it is a good practice to wrap the function inside a try-catch
. Inside the try
, we call the function to insert data
:this.db.collection(collection).insertOne(data);
If the data insertion is successful, it returns a success text. If it fails, it returns a failure text.
Then, inside the catch, we throw the error if something bad happened to the MongoDB when executing the insertOne()
code.
Index.js
Next, we must modify POST /user
, to enable the API to insert data to the database. See this snippet below:
// index.js
...
const collection = 'cil-users';
...
app.post('/user', async (req, res) => {
const result = await mongoDbConnector
.insertOne(collection, req.body);
res.send(result);
});
...
We only need to specify which collection that will be inserted, let’s name it “cil-users”. And we put it before the initializations.
Next, we modify app.post('/user')
. Add async
to the callback function, and then call await mongoDbConnector.insert()
. The collection is “cil-users” and the data is obtained from req.body
. After getting the result, we return it as the response from this endpoint by using res.send()
.
Test the code
Run npm start
on the terminal and do some tests. If you do the steps correctly, you should have the results from these screenshots:

Start your NoSQL Booster or Robo 3T. You should see that the new database “cil-rest-api” is created with the collection “cil-users”.

Lastly, check if the data is inserted successfully by running this command in NoSQL Booster:

Get User (Many)
MongoDb Connector
To get the data of multiple users, we will create the function find()
, and we will add it to mongoDbConnector.js
. See this snippet below:
// mongoDbConnector.js
...
class MongoDbConnector {
...
async find(collection, filter) {
try {
const res = await this.db.collection(collection)
.find(filter);
return res.toArray();
}
catch (err) {
throw err;
}
}
...
}
First, we create a find()
function. It needs two parameters: collection
and filter
.
is the query for finding the data in JSON format, for examplefilter
.{ username: 'johndoe' }
Next, we create a try-catch
block to detect any errors that might happen. Inside the block, we call the function db.collection().find()
with filter
argument. This function will return a list of data which fulfil the filter
. Lastly, the result will be returned after converted to an array of object with toArray()
.
Index.js
Now, let’s integrate the find()
function from MongoDbConnector
to the endpoint GET /user
. Here is how we do it:
// index.js
...
app.get('/user', async (req, res) => {
const result = await mongoDbConnector.find(collection, {});
res.send(result);
});
...
So, we implement it in app.get("/user")
. First, we change the callback function to be an asynchronous function by adding async. Then, we call mongoDbConnector.find(collection, {});
we just wrote before to get the list of users. Finally, we return result
as the response for this endpoint.
Test the code
Now, open your Postman and call GET http://localhost:3000/user
. If you do it correctly, you should see something like this:

Get User (One)
MONGODB CONNECTOR
Let’s add a function called findOne()
. See this snippet below:
// mongoDbConnector.js
class MongoDbConnector {
...
async findOne(collection, filter) {
try {
const res = await this.db.
collection(collection).findOne(filter);
return res || 'Data not found.';
}
catch (err) {
throw err;
}
}
...
}
findOne()
is also an asynchronous function, because we would need to wait for the response from the database. It needs two arguments: collection
and filter
.
In the function, just like insert()
before, we use try-catch
. Then we call the collection
to call findOne()
with the filter
sent from the parameters. If the document is found successfully, return the document, or it will return “Data not found.”
index.js
Back to index.js
. Now, we will enable the findOne()
code in MongoDbConnector
to be accessed from GET /user/:id
. See this snippet:
// index.js
...
const MongoDbConnector = require('./mongoDbConnector');
const { ObjectId } = require('mongodb');
...
app.get('/user/:id', async (req, res) => {
const filter = { _id: ObjectId(req.params.id) };
const result = await mongoDbConnector
.findOne(collection, filter);
res.send(result);
});
...
We get this task done by modifying app.get("/user/:id")
. First, set the callback function as an asynchronous function. Then, we call mongoDbConnector.findOne()
with the arguments collection
and filter
.
Because we would like to find the user by id, we set filter
as { _id: ObjectId(req.params.id) }
. Note that the id here is auto-generated from MongoDB, so it has to be in the format of ObjectId
. Which is why we need to import ObjectId
from mongodb
library.
Lastly, we get the result from the connector and pass it back as a response through res.send()
.
Test the code
Switch to Postman, and hit GET http://localhost:3000/user/<_id>
. You can get the _id
from the database. If it finds the document, it will show something like this:

If the document is not found, it will show that “No data is found”, like this screenshot below:

Update Existing User
Mongodb connector
Now, we want to update existing users. Let’s create function updateOne()
in mongoDbConnector.js
. See this snippet below:
// mongoDbConnector.js
class MongoDbConnector {
...
async updateOne(collection, filter, data) {
try {
const res = await this.db.collection(collection)
.updateOne(filter, { $set: data });
const { nModified, ok } = res.result;
return nModified && ok ?
'Successfully updated data' :
'Failed to update data';
}
catch (err) {
throw err;
}
}
...
}
Function updateOne()
requires three parameters: collection
, filter
, and data
. filter
is for selecting which existing data we want to update, and data
will add or replace the properties of the document selected by filter
.
As usual, we add a try-catch
block to catch errors if it happens. Inside the try block, we use .updateOne(filter, { $set: data });
to execute the update on the database. The second argument requires the expression $set
to update the data. See the documentation for more details.
The result
obtained from .updateOne()
will be deconstructed to check whether the execution succeeded or not. If the value of nModified
is more than 0 and ok
is also more than 0, it will return “Successfully updated data”. If it isn’t, it will return “Failed to update data”.
INDEX.JS
Next, we move back to index.js
to integrate updateOne()
to PATCH /user/:id
. See this snippet below:
// index.js
...
app.patch('/user/:id', async (req, res) => {
const result = await mongoDbConnector.updateOne(
collection,
{ _id: ObjectId(req.params.id) },
req.body
);
res.send(result);
});
...
We add more details on app.patch('/user/:id')
. Set the callback function as an asynchronous function by adding async
. Then call mongoDbConnector.updateOne()
. It requires the collection
name, the filter
by id, and the data
to be added or replaced if the document is found.
Just as the same with the previous section Get User (One), finding by _id
requires changing the format to MongoDB ObjectId()
. The last data
argument is taken from req.body
, which is an object.
Finally, whatever result returned from mongoDbConnector.updateOne()
will be sent as the response for PATCH /user/:id
.
Test the code
Now, run npm start
and open your Postman app and test the endpoint. First, get an existing document with GET /user/:id
we wrote on the previous section. See this screenshot:

Second, do the update with PATCH /user/:id
, you can put any update you want, it will add non-existent property and change the value of existing property:

Lastly, check the document with the same id. You will see the updated and added properties on the document:

Delete Existing User
Mongodb Connector
Last part. We will add the delete part by adding deleteOne()
on mongoDbConnector.js
. See this snippet below:
// mongoDbConnector.js
class MongoDbConnector {
...
async deleteOne(collection, filter) {
try {
const res = await this.db.collection(collection)
.deleteOne(filter);
const { n, ok } = res.result;
return n > 0 && ok ?
'Successfully deleted data' :
'Failed to delete data';
}
catch (err) {
throw err;
}
}
...
}
deleteOne()
requires two parameters: collection
and filter
. By now, you should already know what these parameters do.
Inside the function add a try-catch
block. Then, to delete the document in the database, use db.collection(collection).deleteOne(filter)
.
The result from this operation will be used to return “Successfully deleted data” if res.result.n > 0 && res.ok
, and “Failed to delete data” if the condition is not fulfiled.
Index.js
Back to index.js
, the request we use is DELETE /user/:id
. So, we will modify app.delete()
to become something like this snippet below:
// index.js
...
app.delete('/user/:id', async (req, res) => {
const result = await mongoDbConnector.deleteOne(
collection,
{ _id: ObjectId(req.params.id) }
);
res.send(result);
});
...
Set the callback function as an asynchronous function by adding async
. Then, add mongoDbConnector.deleteOne()
to call the function from mongoDbConnector.js
we wrote before. The filter
is _id
, which is obtained from req.params.id
. Lastly, we return result as the response of DELETE /user/:id
.
Test the Code
These screenshots will give you a picture of what a successful DELETE /user/:id
request should be:

First, we get any document inserted to the database by using GET /user/:id
. Make sure the document exists, because we want to make sure it’s deleted later on.

Next, we execute DELETE /user/:id
. “Successfully deleted data” will show if the deletion executed successfully.

Then, we check again if the deletion really worked. Execute GET /user/:id
again. If “Data not found” shows up, then the operation is successful.
Conclusion
Phew, quite a long post, this one. I’m very sure we all learnt a lot this time also. Here’s a quick recap of the lessons learned:
- MongoDB installation
- Create a MongoDB Connector class
- Connecting to MongoDB
- Integrate some MongoDB CRUD operations in Node.js
- Integrate MongoDB Connector to API routes
And here is the source code for this post. What have you missed? Or maybe do better? Let me know in the comments!
Next post in the series:
Part 3 – Caching With Redis
“Errors using inadequate data are much less than those using no data at all.”
― Charles Babbage