Build a REST API with Node.js and Express

Last updated: January 19, 2023.

A REST API is an API that accepts HTTP requests of various types from the client (GET, POST, PUT and DELETE).

In response, a REST API sends data to the client.

Table of contents

Setting up the endpoints

First, from the command line, you’ll want to install a new Node project in a new project folder and install the packages express and cors (a package for use with express).

cd C:\path\to\your\project-folder
npm init --y
npm i express cors

We’ll use the cors package to prevent any CORS (cross-origin resource sharing) errors from occuring when somebody accesses the API. In other words, we’ll use it to accept requests coming from any IP.

Below is the basic architecture of a REST API using Node.js and Express.

Note that all endpoints are set up on the same path: /api.

const express = require('express');
const cors = require('cors');

const app = express();
app.use(cors());

app.get('/api', (req, res) => {
  console.log("HTTP GET request received")
})

app.post('/api', (req, res) => {
  console.log("HTTP POST request received")
})

app.put('/api', (req, res) => {
  console.log("HTTP PUT request received")
})

app.delete('/api', (req, res) => {
  console.log("HTTP DELETE request received")
})

app.listen(3000, () => {
    console.log("App listening on port " + 3000);
})

To start the app, run node app from the command line. If successful, you should see the message App listening on port 3000.

Because the app is running locally on your machine, it is available at http://localhost:3000/api.

Now, in frontend JavaScript, run the following code to make a request to each of the endpoints:

fetch('http://localhost:3000/api', {
    method: "GET",
})

fetch('http://localhost:3000/api', {
    method: "POST",
})

fetch('http://localhost:3000/api', {
    method: "PUT",
})

fetch('http://localhost:3000/api', {
    method: "DELETE",
})

Now, in the command line, you should see the following output:

HTTP GET request received
HTTP POST request received
HTTP PUT request received
HTTP DELETE request received

If so, the endpoints are accessible and we can now start to construct meaningful responses to the incoming requests.

Responding to requests

A single GET endpoint

In the previous example, what is missing from a REST API is responses sent to the client based upon the read and writing of data.

To add data manipulation to the example, create a new directory in your project project data/counters.

Inside here, create a test.json file with the following contents:

{ "count": 0 }

Now, for the GET endpoint, we’ll read that JSON file and return it to the user in its original JSON format:

const express = require('express');
const cors = require('cors');
const fs = require('fs');

const app = express();
app.use(cors());

app.get('/api', (req, res) => {
    
    fs.readFile(`data/counters/test.json`, 'utf-8', (err, data) => {
        if (err) {
            res.status(404);
            return res.json(`Error reading test.json`);
        }
        res.status(200);
        return res.send(data);
    })

})

app.listen(3000, () => {
    console.log("App listening on " + 3000);
})

Now, on the client-side, make a request to the GET endpoint and log its contents to the console:

fetch('http://localhost:3000/api', {
    method: "GET",
})
.then(res => res.json())
.then(data => console.log(data))

This gives the following output in the browser console:

{ "count": 0 }

Multiple endpoints (GET, POST, PUT and DELETE)

The response for the other endpoints can be constructed in the same way.

The code below is the full code for a REST API counter app that creates and increments (two GET endpoints), creates (POST), updates (PUT) and deletes (DELETE) counters in JSON format from data/counters.

Note that many of the endpoints are now have a dynamic path (e.g. /api/:counterID). This is a way of allowing a user accessing the API to specify which counter they want to access.

The user input is accessible in the request via req.params.counterID.

So if the user accessed http://localhost:3000/api/123, you would have 123 available to you in the function.

You can change :counterID to another valid variable name and the user input would then be accessible via this different property name on req.params.

You’ll also need to install an additional package from the command line to run the code below: uuid, a package for generating very unique IDs:

npm i uuid

Now, try running the code:

const express = require('express');
const cors = require('cors');
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');

const app = express();
app.use(cors());
app.use(express.json());

app.get('/api/:userID', (req, res) => {
    
    fs.readFile(`data/counters/${req.params.userID}.json`, 'utf-8', (err, data) => {
        if (err) {
            res.status(404);
            return res.json(`No counter with id ${req.params.userID}`);
        }
        res.status(200);
        return res.send(data);
    })

})

app.get('/api/:userID/increment', (req, res) => {
    
    fs.readFile(`data/counters/${req.params.userID}.json`, 'utf-8', (err, data) => {
        if (err) {
            res.status(404);
            return res.json(`No counter with id ${req.params.userID}`);
        }
        const obj = JSON.parse(data);
        obj.count = obj.count + 1;
        fs.writeFile(`data/counters/${req.params.userID}.json`, JSON.stringify(obj), (err) => {
            if (err) {
                res.status(500);
                return res.json(`Error updating counter`);
            }
            res.status(200);
            res.json(obj);
        })
    })

})

app.post('/api', (req, res) => {

    const id = uuidv4();

    const counter = { count: 0 };

    fs.writeFile(`data/counters/${id}.json`, JSON.stringify(counter), {flag: "wx"}, (err) => {
        if (err) {
            res.status(500);
            return res.json(`Error creating counter`);
        }
        res.status(200);
        res.json(`Counter created with ID ${id}`);
    })

})

app.put('/api/:userID', (req, res) => {

    fs.readFile(`data/counters/${req.params.userID}.json`, 'utf-8', (err, data) => {
        if (err) {
            res.status(500);
            return res.json(`Counter with ID ${req.params.userID} does not exist`);
        }
        const obj = JSON.parse(data);
        obj.count = req.body.newCount;

        fs.writeFile(`data/counters/${req.params.userID}.json`, JSON.stringify(obj), (err)=> {
            if (err) {
                res.status(500);
                return res.json(`Failed to update counter`);
            }
            res.status(200);
            return res.json(obj);
        })
    })    

})

app.delete('/api/:counterID', (req, res) => {

    fs.unlink(`data/counters/${req.params.counterID}.json`, (err) => {
        if (err) {
            res.status(500);
            return res.json(`No count with ID ${req.params.counterID} found`)
        }
        res.status(200);
        return res.json(`Count with ID ${req.params.counterID} was deleted`);
    })

})

app.listen(3000, () => {
    console.log("App listening on " + 3000);
})

Now, let’s make some example requests to the counter API.

To create a new counter:

fetch('http://localhost:3000/api', {
    method: "POST",
})
.then(res => res.json())
.then(data => console.log(data))
// Counter created with ID 797318e9-1714-4df0-b9d7-1c86fef9aecf

To access that counter’s value:

fetch('http://localhost:3000/api/797318e9-1714-4df0-b9d7-1c86fef9aecf', {
    method: "GET",
})
.then(res => res.json())
.then(data => console.log(data))
// { count: 0 }

To increment its value by 1:

fetch('http://localhost:3000/api/797318e9-1714-4df0-b9d7-1c86fef9aecf/increment', {
    method: "GET",
})
.then(res => res.json())
.then(data => console.log(data))
// { count: 1 }

To set the counter to a new value:

fetch('http://localhost:3000/api/797318e9-1714-4df0-b9d7-1c86fef9aecf', {
    method: "PUT",
    body: JSON.stringify({ newCount: 100 }),
    headers: {
        "Content-type": "application/json",
    }
})
.then(res => res.json())
.then(data => console.log(data))
// { count: 100 }

To delete the counter:

fetch('http://localhost:3000/api/797318e9-1714-4df0-b9d7-1c86fef9aecf', {
    method: "DELETE",
})
.then(res => res.json())
.then(data => console.log(data))
// Count with ID 797318e9-1714-4df0-b9d7-1c86fef9aecf was deleted

And that’s it. Happy…resting!

Related posts