tutorial // Mar 25, 2022
How to Use MongoDB with Node.js
How to connect a Node.js app to an existing MongoDB database using Node.js.

Getting Started
Because the code we're writing for this tutorial is "standalone" (meaning it's not part of a bigger app or project), we're going to create a Node.js project from scratch. If you don't already have Node.js installed on your computer, read this tutorial first and then come back here.
Once you have Node.js installed on your computer, from your projects folder on your computer (e.g., ~/projects
), create a new folder for our work:
Terminal
mkdir mongodb-tutorial
Next, we want to install two dependencies, mongodb
and express
:
Terminal
npm i mongodb express
The first will give us access to the Node.js driver for MongoDB (what we'll use to connect to the database in our code) and the second, Express, will be used to spin up a demo server.
One last step: in the package.json
file that was created for you, make sure to add the field "type": "module"
as a property. This will enable ESModules support and allow us to use the import
statements shown in the code below.
With that in place, we're ready to get started.
Installing and Starting MongoDB
Before we dig into the code, it's important that you have MongoDB installed and accessible on your computer. If you don't already have MongoDB installed, follow the instructions for the "Community Edition" for your operating system here.
Note: for this tutorial, you only need to ensure that MongoDB is installed. You do not need to follow the instructions on starting MongoDB as a background service. If you understand what this means you're more than welcome to, but we'll cover a different way to start the server next.
Starting a MongoDB Server
Before we start the MongoDB server, we need to have a directory accessible where MongoDB can store the data it generates. From the root of the project we just created under "Getting Started" we want to create a directory data
and inside of that, we want to create another directory db
. After you're done, your directory structure should look something like this:
/mongodb-tutorial
-- /data
---- /db
Once you have this, in a terminal window, cd
into the root of the project folder (mongodb-tutorial
) and run the following:
Terminal
mongod --dbpath ./data/db
After running this, you should see some logging from MongoDB which will stop after a few seconds, signifying the server is up and running. Note: this will start MongoDB on its default port 27017
. Knowing that will come in handy next when we wire up the MongoDB connection in our app.
Wiring Up the MongoDB Adapter in Node.js
In order to integrate MongoDB into our app, the first—and most important thing—we need to do is set up a connection to MongoDB using their official Node.js package (known as a "driver," a term commonly used to refer to the package or library used to connect to a database via code).
/connectToMongoDB.js
import { MongoClient } from "mongodb";
const connectToMongoDB = async (uri = '', options = {}) => {
if (!process.mongodb) {
const mongodb = await MongoClient.connect(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
ssl: process.env.NODE_ENV === "production",
...options,
});
const db = mongodb.db('example');
process.mongodb = db;
return {
db,
Collection: db.collection.bind(db),
connection: mongodb,
};
}
return null;
};
export default await connectToMongoDB('mongodb://localhost:27017', {});
Starting at the top of our file, the first thing we want to do is import the named export MongoClient
from the mongodb
package we installed via NPM earlier. The "named" export part is signified by the curly braces wrapping the variable name where as no curly braces would suggest a "default" export.
Next, we want to define a function that will be responsible for establishing the connection to our database. Here, we've defined an arrow function connectToMongoDB()
which takes two arguments: uri
and options
.
Here, uri
refers to the MongoDB connection string. This is a special URI that MongoDB recognizes and explains where the MongoDB driver can find a running MongoDB database to connect to. For options
, these are any special configuration options we want to pass to the driver (e.g., overrides of defaults or options not set here in the tutorial).
Inside of the function, first, we make sure that we don't have an existing process.mongodb
value. This is a convention we're introducing for this tutorial. As we'll see, our goal will be to make our MongoDB database accessible on the process object so that, if we wish, we can access our MongoDB connection globally in our app. The benefit of this will be that we can "reuse" the same connection throughout our app which reduces the overall strain on the MongoDB server.
If we don't already have a value set to process.mongodb
, next, we want to tell the driver to connect to the passed uri
along with some default options. To do that, we call to MongoClient.connect()
passing the uri
(the same one passed to our connectToMongoDB()
function as the first argument) we want to connect to as the first argument, followed by an object containing the options for that connection as the second argument.
Note: we expect this function to return a JavaScript Promise, so, we've made use of the short-hand
async/await
pattern in JavaScript to keep our code clean. In order to make this work, we place theasync
keyword at the start of the function inside of which we'll use theawait
keyword and then place theawait
keyword in front of the function who's Promise we want to "wait on" before evaluating the reset of our code.
To the options object we're passing as the second argument, we've passed three defaults:
useNewUrlParser
which tells the driver to respect the newermongodb+srv://
style of connection URL.useUnifiedTopology
which tells the driver to use the new, more efficient "topology" (MongoDB's internal name for the core parts of the database) which combines all of the important parts of the DB together into one piece.ssl
which tells MongoDB whether or not it should only accept connections over a secure SSL connection. Here, set totrue
only if the value ofprocess.env.NODE_ENV
is"production"
.
Finally, beneath these defaults, we use the JavaScript spread ...
operator to say "take any options passed and spread (or "copy") them onto the object we're passing here." In other words, any properties defined on the options
object we've passed as the second argument to connectToMongoDB
will be copied onto the options object we're passing to MongoClient.connect()
. Additionally, if you want to configure one of the three default options listed above differently, this pattern will automatically overwrite the defaults if you specify a value (e.g., if you set useUnifiedTopology: false
on your options
object, that would override the default true
version).
Next, with our connection (presumably) accessible in the mongodb
variable we assigned our await MongoClient.connect()
call to, next, we create another variable db
and assign it to mongodb.db('example')
where example
is an arbitrary database name that we want to connect to on our MongoDB server (this should be replaced with the name of your own database).
We call this here as it gives us short-hand access to the MongoDB database we're connecting to which avoids us having to write out the .db('<database>')
part in every query we want to run. Next, after this, we assign that db
value to process.mongodb
(remember we hinted at this earlier). This now gives us global access to our MongoDB database throughout our entire app.
One more step: from our function, we want to return an object which gives us access to our MongoDB connection in various ways. This gives us flexibility in our code so we're not stuck with limited access to the database.
On that object, we've defined three properties:
db
which is thedb
variable we just created and explained above.Collection
which is a "hack," which allows us to quickly create a handle for a specific collection in our database.connection
which is the raw connection we established withMongoClient.connect()
.
Finally, at the bottom of our connectToMongoDB()
function, we return null
if process.mongodb
is already set.
One more thing in this file before we move on. You'll notice that at the very bottom of the file, we're adding a default export of a call to our connectToMongoDB()
function. This is intentional. This allows us to establish a connection to MongoDB automatically wherever this file is imported in our app. If we look, we're hardcoding the URI for our MongoDB database as the first argument passed to the function mongodb://localhost:27017
.
This will be passed to connectToMongoDB()
as the uri
argument and, ultimately, become the database that the driver tries to connect to. Because we used the async
keyword in front of connectToMongoDB()
, when called, it will itself return a JavaScript Promise object, so, in front of our call at the bottom of the file, we use the await
keyword again to say "wait for the connection to establish before exporting the value."
With that, our connection is all set. Next, we're going to look at some examples of putting it to use in our app.
Creating a Collection and Test Data
First, in order to demonstrate our connection, we'll need some test data to work. This is a great opportunity to see how the custom Collection
function we exported from our /connectToMongoDB.js
file works.
/books.js
import MongoDB from './connectToMongoDB.js';
const Books = MongoDB.Collection('books');
if (await Books.countDocuments() < 3) {
await Books.bulkWrite([
{
insertOne: {
document: {
title: 'The Culture We Deserve',
author: 'Jacques Barzun',
year: '1989',
},
},
},
{
insertOne: {
document: {
title: 'The Fabric of Reality',
author: 'David Deutsch',
year: '1998',
},
},
},
{
insertOne: {
document: {
title: 'The Bitcoin Standard',
author: 'Saifedean Ammous',
year: '2018',
},
},
}
])
}
export default Books;
First, at the top of our file, we've imported the default export from the /connectToMongoDB.js
file we wrote above (the result of calling await connectToMongoDB()
). In the MongoDB
variable here, we expect to have the object that we returned from our connectToMongoDB()
function.
Remember that on that object, we added a special property Collection
which gives us an easy way to connect to a MongoDB collection with less code. Here, in order to create a handle for a new collection books
, we call to MongoDB.collection('books')
. This does two things:
- Creates the
books
collection in MongoDB if it doesn't already exist. - Returns the collection handle for use elsewhere in our code.
By "handle" we mean a reference back to the collection. We can see this handle put to use just below this where we attempt to seed the database with some test data. Here, we say "if Books.countDocuments()
returns a number less than three, insert the following documents into that collection."
Without this, we'd have to write something like...
await process.mongodb.collection('books').countDocuments();
or
MongoDB.db.collection('books').countDocuments();
Much more concise thanks to our Collection
function.
Though it's not terribly relevant to our work here, inside of the if
statement, assuming that we do not have three existing books, we call to the .bulkWrite()
method MongoDB provides as part of the driver, inserting three books for our test data.
The important part: at the bottom of our file, we take the Books
variable we stored our collection handle in and export it as the default value from our file. This will come in handy next when we read some data back from the database.
Reading Data
To finish up, now, we want to demonstrate reading data from MongoDB using the collection handle we just established with MongoDB.Collection()
. To do it, we're going to wire up a simple Express.js app with a single route /books
where we can retrieve the current list of books in our collection.
/index.js
import express from 'express';
import Books from './books.js';
const app = express();
app.get('/books', async (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.status(200);
res.send(JSON.stringify({ books: await Books.find().toArray() }, null, 2));
});
app.listen(3000, () => {
console.log('App running on localhost:3000');
});
A quick overview of the Express parts: here, we import express
from the express
package we installed earlier and then create a new instance by calling express()
as a function and storing that instance in the variable app
.
Next, at the bottom of our file, we start our Express.js server on port 3000
by calling app.listen()
and providing a callback function where we log out a message to our terminal to let us know the server is running.
The part we care about here: in the middle, we've added a call to app.get()
which defines a route in our application /books
which supports an HTTP GET
request. For that route, we've defined a handler function (pay attention to the usage of async
in front of the function, signifying that we'll use await
somewhere inside of the function) which is designed to respond with a list of our books.
To do it, we make sure to set the Content-Type
header on the res
ponse object to application/json
, then provide an HTTP status code of 200
(meaning ok
or success
) and then finally, call to res.send()
, passing a JSON.stringify()
call, to which we're passing an object with a property books
which is assigned to the result of calling await Books.find().toArray()
which leverages the Books
handler we created in the previous step to perform a query on our books collection.
That's it! If we make sure our MongoDB database is up and running and then start up this server with node index.js
from our terminal (you will need one terminal window/tab for MongoDB and one for this Express server), we should see our books displayed if we visit http://localhost:3000/books
.
Wrapping Up
In this tutorial, we learned how to wire up a connection to a MongoDB database using the official mongodb
package. We learned how to write a wrapper function to help us establish that connection along with some convenience methods to make interacting with MongoDB easier in our code. We also learned how to create a new collection and seed it with some data, as well as how to read data back from a collection via a route in Express.js.