Building a REST API server in Node.js with Express and MongoDB

In this post, we will build a RESTful API server from scratch with Node.js. We will use the Express library as the application server and MongoDB as the database.

banner image

Overview

Let's start with the sample application that we're going to build: an inventory manager. Using our API we will be able to create new items, list down their stock, and add or remove items from the stock when they're purchased/replenished.

architecture diagram

To keep the app as simple as possible, each item will be represented by an object with three fields:

  • id - which is the unique identifier of each item
  • name - the name of the item
  • quantity - the number of items remaining in stock

Designing our REST APIs

Now that we know what we're building, let's try to break it down into individual APIs we're going to need. Each API is represented in the form:

<method> <route>

<body>

Creating a new item

This API will be used to create new items in the inventory. To do this, we will need to receive an HTTP POST request of the form:

POST /item

{
        name: <string>,
        quantity: <number>
}

Our application will generate the ID and return the complete object as the response:

{
        id: <string>,
        name: <string>,
        quantity: <number>
}

Listing all items

This API will fetch and list the information of all the items that we currently have in our inventory. This will be a simple GET request of the form:

GET /items

And will return the response:

[
        {
                id: <string>,
                name: <string>,
                quantity: <number>
        },
        {
                id: <string>,
                name: <string>,
                quantity: <number>
        },
        //...
]

Modifying item quantities

This API will modify the quantity of items that are already in our inventory, for which we will use the PUT method:

PUT /item/{id}/quantity/{quantity}

We specify the ID of the item, as well as the quantity we wish to add in the URL params. SO, if we want to add 3 more items with ID abc123 the API would be:

PUT /item/abc123/quantity/3

Initializing our application

Start by creating a new folder for your application. Within the folder, run the command:

npm init

This will add a package.json file that contains meta information about your project, and its dependencies. Next, let's install all the libraries we'll be using for this project, by running the command:

npm install express mongodb body-parser

This will install the Express.js library, the MongoDB driver library, as well as some other utility libraries like body-parser, that is used to extract the request body as a JSON object.

Interacting with MongoDB

First, let's add a file db.js that will contain all the functions that we will use to interact with our database. Let's add an init function that initializes the database connection:

// import the `MongoClient` object from the library
const { MongoClient } = require('mongodb')

// define the connection string. If you're running your DB
// on your laptop, this would most likely be it's address
const connectionUrl = 'mongodb://localhost:27017'

// Define the DB name. We will call ours `store`
const dbName = 'store'

// Create a singleton variable `db`
let db

// The init function retruns a promise, which, once resolved,
// assigns the mongodb connection to the `db` variable
const init = () =>
  MongoClient.connect(connectionUrl, { useNewUrlParser: true }).then((client) => {
    db = client.db(dbName)
  })

The init function will be called once when the application starts, in order to establish the initial connection. In order to support our APIs for creating,listing, and updating items, we will add functions to insert, get, and update the quantity of items in the database:

// these functions are added to the `db.js` file:

// Take the item as an argument and insert it into the "items" collection
const insertItem = (item) => {
  const collection = db.collection('items')
  return collection.insertOne(item)
}

// get all items from the "items" collection
const getItems = () => {
  const collection = db.collection('items')
  return collection.find({}).toArray()
}

// take the id and the quantity to add as arguments, and increase the
// "quantity" using the `$inc` operator
const updateQuantity = (id, quantity) => {
  const collection = db.collection('items')
  return collection.updateOne({ _id: ObjectId(id) }, { $inc: { quantity } })
}

Each of these functions return the result of the database methods like insertOne, find and updateOne, which are all promises. We consider the operation to be done once these promises resolve.

Creating HTTP routes

We will create all our HTTP routes in a new file routes.js. We'll start off by initializing our dependencies:

// Import the express library, and the functions we defined in our `db.js` file earlier
// along with the "joi" library, which we use for validation
const express = require('express')
const Joi = require('@hapi/joi')
const { insertItem, getItems, updateQuantity } = require('./db')

// Initialize a new router instance
const router = express.Router()

// Define the schema for the item
// Each item will have a `name`, which is a string
// and a `quantity`, which is a positive integer
const itemSchema = Joi.object().keys({
  name: Joi.string(),
  quantity: Joi.number().integer().min(0)
})

POST /item

Let's start with the POST /item route to create a new item:

// A new POST route is created using the `router` objects `post` method
// providing the route and handler as the arguments
router.post('/item', (req, res) => {
  // We get the item from the request body
  const item = req.body

  // The itemSchema is used to validate the fields of the item
  const result = itemSchema.validate(item)
  if (result.error) {
    // if any of the fields are wrong, log the error and return a 400 status
    console.log(result.error)
    res.status(400).end()
    return
  }

  // If the validation passes, insert the item into the DB
  insertItem(item)
    .then(() => {
      // Once the item is inserted successfully, return a 200 OK status
      res.status(200).end()
    })
    .catch((err) => {
      // If there is any error in inserting the item, log the error and
      // return a 500 server error status
      console.log(err)
      res.status(500).end()
    })
})

Overall, we perform request body validation to make sure the user request has the fields we are looking for, and in the correct format. We then insert the item into the database, and return the appropriate status code depending on whether it was inserted successfully.

post API flow

GET /items

The GET /items route will query the database for all available items and return the result as JSON:

router.get('/items', (req, res) => {
  // `getItems` returns a new promise which resolves with the result
  getItems()
    .then((items) => {
      // The promise resolves with the items as results
      items = items.map((item) => ({
        // In mongoDB, each object has an id stored in the `_id` field
        // here a new field called `id` is created for each item which 
        // maps to its mongo id
        id: item._id,
        name: item.name,
        quantity: item.quantity
      }))

      // Finally, the items are written to the response as JSON
      res.json(items)
    })
    .catch((err) => {
      // If there is an error in getting the items, we return a 500 status
      // code, and log the error
      console.log(err)
      res.status(500).end()
    })
})

The assumption here is that the number of items is low enough to return the entire list. If the number of items grows to a large amount (more than 100), we may need to use pagination to limit the number of results per API call.

get API flow

PUT /item/:id/quantity/:quantity

This API will take the ID of the item to update and the increase in quantity of that item, and update the quantity stored in the database.

// :id and :quantity are route parameters, which will act as variables
router.put('/item/:id/quantity/:quantity', (req, res) => {
  // We can get the values from the `req.params` object
  const { id, quantity } = req.params

  // The updateQuantity function is called with the id and quantity increment
  updateQuantity(id, parseInt(quantity))
    .then(() => {
      // If the update is successful, return a 200 OK status
      res.status(200).end()
    })
    .catch((err) => {
      // If the update fails, return a 500 server error
      console.log(err)
      res.status(500).end()
    })
})

The ID and quantity here are taken from the route params, and the quantity is converted to an integer before passing it to the updateQuantity function.

put API flow

Finally, we can export our router to be used with our main application:

module.exports = router

Putting everything together

So now that we've built the modules for our database and HTTP routes, it's time to put it together. The index.js file will act as an entry point to initialize and start the application:

const express = require('express')
// The `body-parser` library helps us parse the HTTP request body and provide
// it to our routes as a javascript object
const bodyParser = require('body-parser')
// The init function is imported from our `db.js` file, as well as the routes
// we created in `routes.js`
const { init } = require('./db')
const routes = require('./routes')

// Create a new express app
const app = express()
// Add the body parses middleware, as well as the HTTP routes
app.use(bodyParser.json())
app.use(routes)

// Initialize the database
init().then(() => {
  // Once the database is initialized, start the server by listening
  // on port 3000
  console.log('starting server on port 3000')
  app.listen(3000)
})

The app can be started by running:

node index.js

Testing our APIs

We can make API calls to our application using a tool like curl or Postman.

First, let's create a new item:

POST http://localhost:3000/item
Content-Type: application/json

{
        "name": "soda",
        "quantity": 20
}
## Response:
HTTP/1.1 200 OK
Content-Length: 0

We can list our items by hitting the GET /items API:

GET http://localhost:3000/items
## Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 63

[
  {
    "id": "5d08e96e7ba9232c404ea3ea",
    "name": "soda",
    "quantity": 20
  }
]

Finally, we can try adding to the quantity of items using the update API:

PUT  http://localhost:3000/item/5d08e96e7ba9232c404ea3ea/quantity/3
HTTP/1.1 200 OK
Content-Length: 0

The updated quantity can be seen using the same GET API:

GET http://localhost:3000/items
## Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 63

[
  {
    "id": "5d08e96e7ba9232c404ea3ea",
    "name": "soda",
    "quantity": 23
  }
]

Wrapping up

So far, we've seen how to implement an end-to-end web application that interacts with MongoDB for persistence. If you want to see the complete working code for this application, you can see the Github repository


Liked this article? Share it on: