Building a REST API with Express, Node, and MongoDB

Follow along with this tutorial to add MongoDB Atlas as the data store for your applications, by creating NodeJS and Express Web REST API.

JavaScript is considered the top web development language today, running in millions of applications and websites. One of the building blocks for the JavaScript stack, on many of its variations, is REST API-based communication between the front-end and back-end tiers.

A popular way of implementing the REST API approach uses Express JS as the back-end web server and MongoDB as the document store. This concept easily maps MongoDB’s document model to the REST API payloads which are JSON by nature.

This article will provide a step-by-step tutorial on how to use Express with MongoDB Atlas, our Database-as-a-Service platform, to expose restful API endpoints for our clients.

Table of Contents

The Project Architecture

You can explore the full project in the following GitHub repo:

  • main branch - finished project
  • stub branch - starting point for following this tutorial

Express allows us to create a back-end middle tier running Node.js server exposing REST API routes to our application.

The Express.js server also connects to the MongoDB Atlas cluster via the Node.js Driver. If you wish to learn more about MongoDB and Node.js, read the following article.

Finally, our front-end application will be written in React to use the REST API endpoints hosted on the Express.js server. The application is a Tinder-like application for the sample_airbnb database, containing information on various listings, available as part of the sample datasets you can load into the Atlas cluster.

Main application design

Users can swipe the listing cards to save or drop them and press the “like” button to add likes. Additionally, a double click will show details about the specific listing card presented.

Application

Project Tree

Here are the main files in the project:

  • server/
    • db
      • conn.js
    • routes
      • record.js
    • server.js
    • config.env
  • app/listings/
    • public/
    • src/
      • App.js
      • App.css
      • index.js

The “server” directory hosts the Express.js server application and all of its dependencies. The main files here are:

  • “db/conn.js”: Exposes a global connection to the Atlas database by exporting a MongoDB client that any other module can use.
  • “routes/record.js”: Exposes the REST API endpoints and performs their business logic against the Atlas cluster.
  • “server.js”: The main entry point for the Express server and configuration initialization.
  • “config.env”: Configuration file holding Atlas connection string details.

The “app/listings” directory is where the front-end React application code resides. The main files here are:

  • “App.js/App.css”: Front-end React code to render the app and interact with the Express REST API endpoints.
  • “index.js”: The main entry point for the React web app importing the App.js.

Setting Up the Project

First, you will need to deploy an Atlas cluster. You can follow the Getting Started with Atlas guide to learn how to create a free Atlas account, create your first cluster, and get your connection string to the database.

Once we have the Atlas cluster available, we can load the sample data by clicking [...] > “Load Sample Data.” When data is loaded, we are ready to clone the project stub branch:

git clone -b stub 
git@github.com:mongodb-developer/mongodb-express-rest-api-example.git

Let’s go to the “server” directory of the project and install the needed packages:

cd mongodb-express-rest-api-example/server
npm install

Now, we are ready to connect our Express server to the MongoDB Atlas Cluster.

Connecting to MongoDB Atlas

Once you locate your connection string, create a config.env file in the server directory. There, assign a new ATLAS_URI variable the value of the connection string. Replace the <username> and the <password> with your database username and password. Once done, your file should look similar to the one below.

ATLAS_URI=mongodb+srv://<username>:<password>@sandbox.jadwj.mongodb.net/myFirstDatabase?retryWrites=true&w=majority

Next, open server/db/conn.js and add the implementation of the connectToServer function from the snippet below.

const { MongoClient } = require("mongodb");
const connectionString = process.env.ATLAS_URI;
const client = new MongoClient(connectionString, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

let dbConnection;

module.exports = {
  connectToServer: function (callback) {
    client.connect(function (err, db) {
      if (err || !db) {
        return callback(err);
      }

      dbConnection = db.db("sample_airbnb");
      console.log("Successfully connected to MongoDB.");

      return callback();
    });
  },

  getDb: function () {
    return dbConnection;
  },
};

The main object this module exports out is the _db variable, which will hold the "sample_airbnb" database-level object. Via this object, we will be able to access any collection within that database or change its context to another database. In this tutorial, we will use only a single database named “sample_airbnb.”

Adding REST API CRUD Routes

The main Express.js part in this tutorial is to expose REST API routes to perform Read, Create, Update, and Delete operations for our listing application. This can be extended for more complex application business logic as your use case requires.

The file that will host the routes is --- “server/routes/record.js”. It uses the Express Router feature:

const express = require("express");

// recordRoutes is an instance of the express router.
// We use it to define our routes.
// The router will be added as a middleware and will take control of requests starting with path /listings.
const recordRoutes = express.Router();

Read Route

The Read route will be used when the /listings path on a GET method is called. It will use a collection.find() to query our listingAndReviews collection for the first 50 available listings:

// This section will help you get a list of all the documents.
recordRoutes.route("/listings").get(async function (req, res) {
  const dbConnect = dbo.getDb();

  dbConnect
    .collection("listingsAndReviews")
    .find({}).limit(50)
    .toArray(function (err, result) {
      if (err) {
        res.status(400).send("Error fetching listings!");
     } else {
        res.json(result);
      }
    });
});

The code sends back the result set as the API response.

Create Route

The Create route will record a “match” swipe in a “matches” collection. The body of this POST method will present a user session_id and the swiped direction and listing_id to create a “match” document.

// This section will help you create a new document.
recordRoutes.route("/listings/recordSwipe").post(function (req, res) {
  const dbConnect = dbo.getDb();
  const matchDocument = {
    listing_id: req.body.id,
    last_modified: new Date(),
    session_id: req.body.session_id,
    direction: req.body.direction
  };

  dbConnect
    .collection("matches")
    .insertOne(matchDocument, function (err, result) {
      if (err) {
        res.status(400).send("Error inserting matches!");
      } else {
        console.log(`Added a new match with id ${result.insertedId}`);
        res.status(204).send();
      }
    });
});

The save is done via collection.insertOne() method with the prebuilt “matchDocument.”

You can also use InsertMany to insert multiple documents at once.

Update Route

The Update route updates the “likes” field on a listing object. This is done via a POST method:

// This section will help you update a document by id.
recordRoutes.route("/listings/updateLike").post(function (req, res) {
  const dbConnect = dbo.getDb();
  const listingQuery = { _id: req.body.id };
  const updates = {
    $inc: {
      likes: 1
    }
  };

  dbConnect
    .collection("listingsAndReviews")
    .updateOne(listingQuery, updates, function (err, _result) {
      if (err) {
        res.status(400).send(`Error updating likes on listing with id ${listingQuery.id}!`);
      } else {
        console.log("1 document updated");
      }
    });
});

The method will use the collection.updateOne() method with a $inc on the “like” field to increment the likes.

Delete Route

Whenever a listing is dropped, we can delete it from the database so that it doesn’t appear anymore. This is done via the Delete route.

// This section will help you delete a record.
recordRoutes.route("/listings/delete/:id").delete((req, res) => {
  const dbConnect = dbo.getDb();
  const listingQuery = { listing_id: req.body.id };

  dbConnect
    .collection("listingsAndReviews")
    .deleteOne(listingQuery, function (err, _result) {
      if (err) {
        res.status(400).send(`Error deleting listing with id ${listingQuery.listing_id}!`);
      } else {
        console.log("1 document deleted");
      }
    });
});

The route here includes the :id parameter. This is the id of the listing to be deleted via collection.deleteOne().

Now that we have everything in place, we can launch the server:

npm start
[nodemon] starting `node server.js`
Successfully connected to MongoDB.
Server is running on port: 5000

Setting Up the Front End

Our React application consists mainly of the App.js React file and class.

import './App.css';
import TinderCard from 'react-tinder-card'
import axios from 'axios';
import { Component } from 'react';
import { v4 as uuid } from 'uuid';

class App extends Component {
  constructor() {
    super();

    this.state = {
      data: [],
      session_id: uuid(),
      liked: false
    };
    this.handleClick = this.handleClick.bind(this);
    this.showDetails = this.showDetails.bind(this);
  }

  async onSwipe(direction, listingId, sessionId) {
    this.setState({
      liked: false
    });


    if (direction === "left") {
      await axios.delete(`http://localhost:5000/listings/delete/${listingId}`)
    } else {
      await axios.post("http://localhost:5000/listings/recordSwipe", { id: listingId, session_id: sessionId, direction })
    }
  }

  async handleClick(listingId) {
    this.setState({
      liked: !this.state.liked
    });

    await axios.post("http://localhost:5000/listings/updateLike", { id: listingId });
  }

  showDetails(listing) {
    alert(`Name: ${listing.name}\n Price : $${listing.price['$numberDecimal']} \n Minimum Nights : ${listing.minimum_nights}\n Beds : ${listing.beds}`);
  }

  async componentWillMount() {
    const response = await axios.get(`http://localhost:5000/listings`);
    const json = await response.data;
    this.setState({ data: json });
  }

  render() {
    const likeButtonLabel = this.state.liked ? '❤' : 'Like';

    return (
        <div className="app">
          <div>
            <h1>LisTinder</h1>
            <h2>Swipe left for drop or right to save...</h2>

            <div className="card-container">
            {this.state.data.map((listing) =>
              <TinderCard className='swipe' key={listing.name} onSwipe={(dir) => this.onSwipe(dir, listing._id)}  >
                <div style={{ backgroundImage: 'url(' + listing.images.picture_url + ')' }} className='card'>
                  <div className="card-details">
                    <h3>{listing.name}</h3>
                    <div className="card-actions">
                      <button className="button" onClick={() => this.handleClick(listing._id)}>{likeButtonLabel}</button>
                      <button className="button" onClick={() => this.showDetails(listing)}>See Details</button>
                    </div>
                  </div>
                </div>
              </TinderCard>
            )}
            </div>
          </div>
        </div>
    );
  }
}

export default App;

We will use some third-party modules, like the “react-tinder-card”, which will allow us to create the swiping tiles and graphics. Those will interact with the application functions to handle events like “onSwipe,” “handleLikeClick,” “showDetails,” and “componentWillMount” (to show the data on page load).

Functions “onSwipe,” “handleLikeClick,” and “componentWillMount” will use the axios library to perform http REST API requests to our Express server. Those, in turn, will perform the needed action on our Atlas cluster.

Now we can start the application in a new terminal (the server process must remain running):

cd ../app/listings
npm install
npm start

Testing the Application

Once all components are up and running, we can open the http://localhost:3000 URL and we should see our “LisTinder” application loaded:

Main AppSwipe CardsShow Details
Main Application Swipe Cards Left and RightApp Detail View

Interaction with the tiles by swapping them will call our Express routes and perform application operations.

Successfully connected to MongoDB.
1 document updated
1 document deleted

MongoDB Realm Webhooks as Express Replacement

MongoDB Realm, MongoDB’s mobile database and development cloud services, offer a robust and scalable replacement to the self-hosted Express Server.

Creating an application is very easy with a generous free tier. In that application, you can create HTTP services with webhook endpoints to perform the same operations as the Express routes, without the need for maintaining and coding lots of boilerplate code. Those services are optimised for Atlas cluster access and will open a new world of opportunities like cloud functions, auth providers, GraphQL, and triggers.

Let’s port one route to a webhook. We will start by creating an HTTP service within a newly created Realm application UI.

Create HTTP Service

Navigate to the “3d Party Services” section and click on the HTTP service type.

Create a service

Create Webhook for That Service

As part of defining the service, we need to configure the HTTP method this webhook will use and its associated function logic.

Define a Webhook

The associated function code:

// This function is the webhook's request handler.
exports = async function(payload, response) {

    // Querying a mongodb service:
    return await context.services.get("mongodb-atlas").db("sample_airbnb").collection("listingsAndReviews").find({}).limit(50);

};

Now we can use the webhook URL directly in the React application. Add the following to the app class in App.js:

async componentWillMount() {
  const response = await axios.get(`<PASTE-YOUR-WEBHOOK-URL>`);
  const json = await response.data;
  this.setState({ data: json });
}

Wow, that's much easier and more scalable!

Summary

Using Express as a back-end middleware is a popular MongoDB stack design. Express is lightweight and approachable for JSON and REST API operations. MongoDB Atlas is a scalable and flexible document Database-as-a-Service and makes a perfect companion to Express in many stacks like MERN, MEAN, and MEVN.

Having said that, MongoDB Realm Development Services and webhooks are a robust replacement for the Express tier, moving the need to manage an Express server and its dependencies on-prem.

FAQ

How do I use MongoDB with Express JS?

The MongoDB Driver is the native driver for Node JS applications. Therefore, the Express JS server can use it to access MongoDB.

How do I create a REST API using node Express and Mongo?

In this article, we covered the essential parts of setting up an Express server with connection to MongoDB Atlas as well as exposing REST API endpoints to the client applications.

Want to learn more about MongoDB and Express?

Visit the following links: