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.
Architecture Overview
As an example, we’ll build an inventory manager application. 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 or replenished.
Let’s look at the different components of this application:
- We will define a RESTful API to act as an interface between the user and our application
- The Node.js application will host an HTTP server and the application logic of our APIs
- We will use MongoDB as a database to store and retrieve our data.
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 itemname
- the name of the itemquantity
- 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>
The method and route will change based on our action. For this example, we’ll focus on three different actions:
Action | Method | Route |
---|---|---|
Creating a new item | POST | /item |
Listing all items | GET | /items |
Modifying item quantities | PUT | /item/{id}/quantity/{quantity} |
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.
You should also install MongoDB on your system. By default, the database would run on port 27017
.
Creating the MongoDB Client
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 also add an init
function that initializes the database connection:
// db.js
// 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:
// db.js
// 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 } })
}
// export the required functions so that we can use them elsewhere
module.exports = { init, insertItem, getItems, updateQuantity }
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.
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.
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.
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
}
]
Complete Example Code
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