Build a REST API using Node.js, Express and MongoDB

In this tutorial we’ll create a REST API using Node.js and Express.

This API will expose a set of GET and POST endpoints to allow getting data out, and sending data in.

We’ll use a MongoDB database to store this data.

Tip: make sure you installed a MongoDB database on your system before going on with the tutorial (or use a cloud MongoDB database if you prefer)

Our task is to create a trip cost calculator app.

Imagine going on a trip, and you have your app (which can be a progressive web app, or a mobile app for example) where you add any expense you make. Gasoline, hotels, food, tickets and so on.

When the trip ends, you archive it and it becomes part of the history - which you can navigate and see how much you spent in the past trips.

We’re not going to create the frontend of the application here, just the API.

Let’s now dissect this into details, and translate it into a series of API endpoints.

An endpoint is a unique URL we will call to create an operation.

Like adding a new trip with its name.

At the beginning, there is no trip stored, and we need to add one. I imagine the app will ask the user for the name, and there will be a “Create trip” button. When clicked, the app will send the name to us, to the /trip endpoint with the POST HTTP method.

We have our first endpoint, which will accept a name property.

POST /trip { name }

Another endpoint will list the trips, and it’s

GET /trips

By default, it will return the trips ordered by creation date.

When the user wants to add a new expense, the app will invoke the /expense endpoint with the POST method, with some parameters that describe the expense

POST /expense { trip, date, amount, category, description }

trip is the ID of the currently selected trip.

category is the name of the category of the expense. We’ll provide a list of categories to choose from, which is static: travel, food, accomodation, fun.

When we want to retrieve a trip expenses, we call the /expenses endpoint with the GET method:

GET /expenses { trip }

passing the trip identifier.

Let’s start the project

I am going to use a local installation of Node.js.

We start our Node.js project by going into a new folder (call it tripcost) and typing the command npm init -y.

We’ll use MongoDB as our database.

Install the mongodb Node.js package with:

npm install mongodb

While you’re here, also install Express:

npm install express

Create a server.js file now, where we’ll store our API code, and start requiring Express and MongoDB:

const express = require("express")
const mongo = require("mongodb").MongoClient

Initialize the Express app:

const app = express()

And now we can add the stubs for the API endpoints we support:

app.post("/trip", (req, res) => {
  /* */
})
app.get("/trips", (req, res) => {
  /* */
})
app.post("/expense", (req, res) => {
  /* */
})
app.get("/expenses", (req, res) => {
  /* */
})

Finally, use the listen() method on app to start the server:

app.listen(3000, () => console.log("Server ready"))

You can run the application using node server.js in the project folder.

Adding trips

We offer the client a way to add trip using the POST /trip endpoint:

app.post("/trip", (req, res) => {
  /* */
})

Let’s go ahead and implement it.

We already included the MongoDB library, so we can use it in our endpoint implementation:

const mongo = require("mongodb").MongoClient

Next, we build the MongoDB server URL. If you are running the project locally, and MongoDB locally too, the URL is likely this:

const url = "mongodb://localhost:27017"

because 27017 is the default port.

Next, let’s connect to the database using connect():

let db

mongo.connect(
  url,
  {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  },
  (err, client) => {
    if (err) {
      console.error(err)
      return
    }
    db = client.db("tripcost")
  }
)

and while we’re here, let’s also get a reference to the trips and expenses collections:

let db, trips, expenses

mongo.connect(
  url,
  {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  },
  (err, client) => {
    if (err) {
      console.error(err)
      return
    }
    db = client.db("tripcost")
    trips = db.collection("trips")
    expenses = db.collection("expenses")
  }
)

Now we can go back to our endpoint.

This endpoint expects 1 parameter, name, which represents how we call our trip. For example Sweden 2018 or Yosemite August 2018.

See my tutorial on how to retrieve the POST query parameters using Express

We expect data coming as JSON, using the Content-Type: application/json, so we need to use the express.json() middleware:

app.use(express.json())

We can now access the data by referencing it from Request.body:

app.post("/trip", (req, res) => {
  const name = req.body.name
})

Once we have the name, we can use the trips.insertOne() method to add the trip to the database:

app.post("/trip", (req, res) => {
  const name = req.body.name
  trips.insertOne({ name: name }, (err, result) => {})
})

If you get an error like “could not read property insertOne of undefined”, make sure trips is successfully set in mongo.connect(). Add a console.log(trips) before calling insertOne() to make sure it contains the collection object.

We handle the error, if present in the err variable, otherwise we send a 200 response (successful) to the client, adding an ok: true message in the JSON response:

app.post("/trip", (req, res) => {
  const name = req.body.name
  trips.insertOne({ name: name }, (err, result) => {
    if (err) {
      console.error(err)
      res.status(500).json({ err: err })
      return
    }
    console.log(result)
    res.status(200).json({ ok: true })
  })
})

That’s it!

Now restart the Node application by hitting ctrl-C to stop it, and run it again.

You can test this endpoint using the Insomnia application, a great way to test and interact with REST endpoints:

List the trips

The list of trips is returned by the GET /trips endpoint. It accepts no parameters:

app.get("/trips", (req, res) => {
  /* */
})

We already initialized the trips collection, so we can directly access that to get the list.

We use the trips.find() method, which result we must convert to an array using toArray():

app.get("/trips", (req, res) => {
  trips.find().toArray((err, items) => {})
})

Then we can handle the err and items results:

app.get("/trips", (req, res) => {
  trips.find().toArray((err, items) => {
    if (err) {
      console.error(err)
      res.status(500).json({ err: err })
      return
    }
    res.status(200).json({ trips: items })
  })
})

Here’s the result of the API call in Insomnia:

Add an expense

We previously got the list of trips. Every trip has an associated _id property which is added by MongoDB directly when it’s added:

{
  "trips": [
    {
      "_id": "5bdf03aed64fb0cd04e15728",
      "name": "Yellowstone 2018"
    },
    {
      "_id": "5bdf03c212d45cdb5ccec636",
      "name": "Sweden 2017"
    },
    {
      "_id": "5bdf047ccf4f42dc368590f6",
      "name": "First trip"
    }
  ]
}

We’ll use this _id to register a new expense.

If you remember, the endpoint to add a new expense is this:

POST /expense { trip, date, amount, category, description }

trip in this case will be the _id of one of the trips we previously registered. Imagine that in the app, the user will add one trip, and that will remain the current trip until a new one is added (or selected).

Let’s go ahead and implement our stub:

app.post("/expense", (req, res) => {
  /* */
})

Like when adding a trip, we’re going to use the insertOne() method, this time on the expenses collection.

We get 5 parameters from the request body:

  • trip
  • date the date, in ISO 8601 format (e.g. 2018-07-22T07:22:13), in the GMT timezone
  • amount an integer with the amount
  • category which is one from travel, food, accomodation, fun
  • description a description for the expense, so we’ll remember about it later
app.post("/expense", (req, res) => {
  expenses.insertOne(
    {
      trip: req.body.trip,
      date: req.body.date,
      amount: req.body.amount,
      category: req.body.category,
      description: req.body.description,
    },
    (err, result) => {
      if (err) {
        console.error(err)
        res.status(500).json({ err: err })
        return
      }
      res.status(200).json({ ok: true })
    }
  )
})

List all expenses

The last piece of the puzzle is getting the expenses.

We need to fill the /expenses endpoint stub, the last one missing:

app.get("/expenses", (req, res) => {
  /* */
})

This endpoint accepts the trip parameter, which is the _id property of a trip stored in the database.

app.get("/expenses", (req, res) => {
  expenses.find({ trip: req.body.trip }).toArray((err, items) => {
    if (err) {
      console.error(err)
      res.status(500).json({ err: err })
      return
    }
    res.status(200).json({ expenses: items })
  })
})

This Insomnia screenshot shows it at work:

Download my freeNode.js Handbook!

Download my free Programming Ebooks!