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.

How to Use MongoDB with 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.

UvQu1o34rchWtgXC/3SY5asLqgb1oSet6.0
Finding the appropriate MongoDB installation tutorial for your operating system.

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 the async keyword at the start of the function inside of which we'll use the await keyword and then place the await 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:

  1. useNewUrlParser which tells the driver to respect the newer mongodb+srv:// style of connection URL.
  2. 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.
  3. ssl which tells MongoDB whether or not it should only accept connections over a secure SSL connection. Here, set to true only if the value of process.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:

  1. db which is the db variable we just created and explained above.
  2. Collection which is a "hack," which allows us to quickly create a handle for a specific collection in our database.
  3. connection which is the raw connection we established with MongoClient.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:

  1. Creates the books collection in MongoDB if it doesn't already exist.
  2. 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 response 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.

Written By
Ryan Glover

Ryan Glover

CEO/CTO @ CheatCode