Building a REST API with Node and Express

Introduction

REST APIs are an industry-standard way for web services to send and receive data. They use HTTP request methods to facilitate the request-response cycle and typically transfer data using JSON, and more rarely - HTML, XML and other formats.

In this guide, we are going to build a REST API to manage books with Node.js and Express.

For the sake of simplicity, we won't be using a database, so you don't need experience using one. We will use a simple JavaScript array to store our data instead.

What is a REST API?

REST (Representational State Transfer) is a standard architecture for building and communicating with web services. It typically mandates resources on the web are represented in a text format (like JSON, HTML, or XML) and can be accessed or modified by a predetermined set of operations. Given that we typically build REST APIs to leverage HTTP instead of other protocols, these operations correspond to HTTP methods like GET, POST, or PUT.

On a collection of data, like books for example, there are a few actions we'll need to perform frequently, which boil down to - Create, Read, Update and Delete (also known as CRUD Functionality).

An API (Application Programming Interface), as the name suggests, is an interface that defines the interaction between different software components. Web APIs define what requests can be made to a component (for example, an endpoint to get a list of books), how to make them (for example, a GET request), and their expected responses.

What is Express?

ExpressJS is one of the most popular HTTP server libraries for Node.js, which by default isn't as friendly for API development. Using Express, we simplify API development by abstracting away the boilerplate needed to set up a server, which makes development faster, more readable and simpler. You can spin up a prototype API in seconds and a couple of lines of code.

Although its primary use was to simplify things with sensible defaults, it's highly customizable using functions called "middleware".

Note: Express is very lightweight and is built on top of middleware. Using middleware, you can expand and extend its functionality beyond the functions already present by default.

Even though we are only going to build a REST API in this guide, the ExpressJS framework is not limited to just that - hosting static files, performing server-side rendering, or even using it as a proxy server isn't uncommon and the sky's the limit with additional middleware.

HTTP Request Types

There are a few types of HTTP methods that we need to grasp before building a REST API. These are the methods that correspond to the CRUD tasks:

  • POST: Used to submit data, typically used to create new entities or edit already existing entities.
  • GET: Used to request data from the server, typically used to read data.
  • PUT: Used to completely replace the resource with the submitted resource, typically used to update data.
  • DELETE: Used to delete an entity from the server.

Note: Notice that you can use either POST or PUT to edit stored data. You're free to choose whether you even want to use PUT since it can be omitted fully. Though, stay consistent with the HTTP verbs you use. If you're using POST to both create and update, then don't use the PUT method at all.

What We are Going to Build

Let's create a simple app to store information about books. In this app, we will store information about the ISBN of the book, title, author, published date, publisher and number of pages.

Naturally, the basic functionality of the API will be CRUD functionality. We'll want to be able to send requests to it to create, read, update and delete Book entities. Of course, an API may do much more than this - provide users with an endpoint to get statistical data, summaries, call other APIs, etc.

Non-CRUD functionalities are application-dependent, and based on your project's nature, you'll probably have other endpoints. However, practically no project can go without CRUD.

To avoid making up book data - let's use a dataset from GitHub to get some sample details about books.

Setting Up the Project

First, let's initialize a new Node.js project:

$ npm init

Fill the requested information to your requirements - you don't have to fill all of the fields, but they're an easy way to set up identifiable data for a project. Fields like the name are much more relevant for publishing applications to the Node Package Manager, amongst other fields.

Alternatively, you can use the default settings by adding the -y flag to the call:

$ npm init -y

Either way - you'll end up with a project with a package.json file. This is a json file that contains all the relevant metadata on your project and will look something along these lines by default:

{
  "name": "app",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "keywords": [],
  "description": ""
}

The "entry"/"main" of your application is the file to run to start the project correctly - usually your main script, and index.js by default.

Additionally, the version of your application and "scripts" are here! You can supply any number of custom commands in the scripts section, with a command associated with an alias. Here, the test alias is a wrapper for an echo statement.

You'd run the test of the application via:

$ npm test

> [email protected] test /Users/david/Desktop/app
> echo "Error: no test specified" && exit 1

Error: no test specified

Oftentimes, there's a start alias that masks one or more processes that should be run when we want to start an application. In the basic form - we just run the index page with node:

{
  "name": "app",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  },
  "author": "",
  "license": "ISC",
  "keywords": [],
  "description": ""
}

You can put any number of commands besides node index.js as the start script and when you run npm start - they'll all run:

$ test npm start    

> [email protected] start /Users/david/Desktop/app
> node index.js

Note: Since we only have one command in the starting script, it's functionally equivalent to just calling $ node index.js in the command line to start the application.

Now that you're familiar with the project, let's install Express!

$ npm install --save express

A new file is created in the directory, alongside a node_modules directory. The package-lock.json file keeps track of your dependencies and contains their versions and names:

{
  "name": "app",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "accepts": {
      "version": "1.3.7",
      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
      "requires": {
        "mime-types": "~2.1.24",
        "negotiator": "0.6.2"
      }
    },
    ...

The node_modules directory actually hosts the code of the dependencies, and can get quite large very quickly. Just by installing Express, we've already got a hefty number of modules installed and tracked in the package-lock.json file.

These modules are, factually, small so it's not an issue by any means. By using the package-lock.json file, another client would know which dependencies to download and which versions to use to correctly be able to start up your application.

Note: When doing version control with tools like Git - it's considered a good practice not to version the source code of the modules you use in the application. In practical terms - don't keep track or push node_modules to a repository. Others can download the dependencies based on the crucial package-lock.json which happens automatically when they run the application with npm.

Creating a Simple Endpoint

Now, let's start building a simple "Hello World" app. It'll have a single simple endpoint that just returns a message as a response to our request to get the home page.

First, let's create a file called hello-world.js:

$ nano hello-world.js

Then, let's import the Express framework within it:

const express = require('express');

Next, we'll want to instantiate the Express app:

const app = express();

And set our port:

const port = 3000;

The port will be used a bit later, when we tell the app to listen to requests. These three lines are boilerplate - but the great thing is, that's all the boilerplate there is!

Now, we can create a simple GET endpoint right beneath the boilerplate. When a user hits the endpoint with a GET request, the message "Hello World, from express" will be returned (and rendered in the browser or displayed on the console).

We'd like to set it to be on the home page, so the URL for the endpoint is /:

app.get('/', (req, res) => {
    res.send('Hello World, from express');
});

At this point, let's start our clients:

app.listen(port, () => console.log(`Hello world app listening on port ${port}!`))

Let's run the application and visit the only endpoint we have via our browser:

$ node hello-world.js
Hello world app listening on port 3000!

This is technically a working API! Though, this endpoint doesn't really do much. Let's take a look at some common middleware that'll be useful for further work and create some more useful endpoints.

Express Middleware

As mentioned above - ExpressJS is a simple HTTP server and it does not come with a lot of features out of the box. Middleware acts almost like extensions for the Express server and provides additional functionalities in the "middle" of a request. Many third-party extensions like morgan for logging, multer for handling file uploads, are used routinely.

For now, to get started, we need to install a middleware called body-parser, which helps us decode the body from an HTTP request:

$ npm install --save body-parser

It parses the body of the request and lets us react to it accordingly.

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

Since we are calling the API from different locations by hitting endpoints in the browser. We also have to install the CORS middleware.

If you're not yet familiar with cross-origin resource sharing, it is okay for now. Let's just install the middleware and configure it:

$ npm install --save cors

Building a REST API with Node and Express

Adding Books

Now we can start building our app. Create a new file called book-api.js:

const express = require('express')
const bodyParser = require('body-parser');
const cors = require('cors');

const app = express();
const port = 3000;

// Where we will keep books
let books = [];

app.use(cors());

// Configuring body parser middleware
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.post('/book', (req, res) => {
    // We will be coding here
});

app.listen(port, () => console.log(`Hello world app listening on port ${port}!`));

As you can see, we can configure body-parser by importing it and passing it to the app.use method, which enables it as middleware to the Express app instance.

We will be using the books array to store our collection of books, simulating a database.

There are a few types of HTTP request body types. For example, application/x-www-form-urlencoded is the default body type for forms, whereas application/json is something we'd use when requesting a resource using jQuery or a backend REST client.

What the body-parser middleware will be doing is grabbing the HTTP body, decoding the information, and appending it to the req.body. From there, we can easily retrieve the information from the form - in our case, a book's information.

Inside the app.post method let's add the book to the book array:

app.post('/book', (req, res) => {
    const book = req.body;

    // Output the book to the console for debugging
    console.log(book);
    books.push(book);

    res.send('Book is added to the database');
});

Now, let's create a simple HTML form with the fields: ISBN, title, author, published date, publisher, and number of pages in a new file, say new-book.html.

We'll be sending the data to the API using this HTML form's action attribute:

<div class="container">
    <hr>
    <h1>Create New Book</h1>
    <hr>

    <form action="http://localhost:3000/book" method="POST">
        <div class="form-group">
            <label for="ISBN">ISBN</label>
            <input class="form-control" name="isbn">
        </div>

        <div class="form-group">
            <label for="Title">Title</label>
            <input class="form-control" name="title">
        </div>

        <!--Other fields-->
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
</div>

Here, our <form> tag's attribute corresponds to our endpoint and the information we send with the submit button is the information our method parses and adds to the array. Note that the method parameter is POST, just like in our API.

You should see something like that when you open the page:

Clicking "Submit", we're greeted with the our applications console.log(book) statement:

{ isbn: '9781593275846',
  title: 'Eloquent JavaScript, Second Edition',
  author: 'Marijn Haverbeke',
  publish_date: '2014-12-14',
  publisher: 'No Starch Press',
  numOfPages: '472' }

Note: Please note that since we are using an array to store data we will lose them in our next app restart.

Getting All Books

Now let's create an endpoint to get all the books from the API:

app.get('/books', (req, res) => {
    res.json(books);
});

Restart the server. If the server is already running press Ctrl + C to stop it first. Add some books and open http://localhost:3000/books in your browser. You should see a JSON response with all the books that you've added.

Now let's create an HTML page to display these books in a user-friendly way.

This time around, we'll create two files - book-list.html which we'll use as a template and a book-list.js file which will hold the logic to updating/deleting books and displaying them on the page:

Let's start off with the template:

<div class="container">
    <hr>
    <h1>List of books</h1>
    <hr>
    <div>
        <div class="row" id="books">
        </div>
    </div>
</div>

<div id="editBookModal" class="modal" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Edit Book</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>

            <div class="modal-body">
                <form id="editForm" method="POST">
                    <div class="form-group">
                        <label for="ISBN">ISBN</label>
                        <input class="form-control" name="isbn" id="isbn">
                    </div>

                    <div class="form-group">
                        <label for="Title">Title</label>
                        <input class="form-control" name="title" id="title">
                    </div>

                    <!--Other fields-->

                    <button type="submit" class="btn btn-primary">Submit</button>
                </form>
            </div>
        </div>
    </div>
</div>
<!--Our JS file-->
<script src="book-list.js"></script>

With the template done, we can implement the actual logic to retrieve all books using browser-side JavaScript and our REST API:

const setEditModal = (isbn) => {
    // We will implement this later
}

const deleteBook = (isbn) => {
    // We will implement this later
}

const loadBooks = () => {
    const xhttp = new XMLHttpRequest();

    xhttp.open("GET", "http://localhost:3000/books", false);
    xhttp.send();

    const books = JSON.parse(xhttp.responseText);

    for (let book of books) {
        const x = `
            <div class="col-4">
                <div class="card">
                    <div class="card-body">
                        <h5 class="card-title">${book.title}</h5>
                        <h6 class="card-subtitle mb-2 text-muted">${book.isbn}</h6>

                        <div>Author: ${book.author}</div>
                        <div>Publisher: ${book.publisher}</div>
                        <div>Number Of Pages: ${book.numOfPages}</div>

                        <hr>

                        <button type="button" class="btn btn-danger">Delete</button>
                        <button types="button" class="btn btn-primary" data-toggle="modal"
                            data-target="#editBookModal" onClick="setEditModal(${book.isbn})">
                            Edit
                        </button>
                    </div>
                </div>
            </div>
        `

        document.getElementById('books').innerHTML = document.getElementById('books').innerHTML + x;
    }
}

loadBooks();

In the above script, we are sending a GET request to the endpoint http://localhost:3000/books to retrieve the books and then creating a Bootstrap card for every book to display it. If everything is working correctly you should see something like this on your page:

You probably noticed the Edit and Create buttons and their respective methods. For now, let's leave them empty and implement them as we go.

Retrieving a Book by ISBN

If we'd like to display a specific book to the user, we'll need a way to retrieve it from the database (or the array, in our case). This is always done by a key specific to that entity. In most cases, each entity has a unique id that helps us identify them.

In our case, each book has an ISBN which is unique by nature, so there's no need for another id value.

This is typically done by parsing the URL parameter for an id and searching for the book with the corresponding id.

For an example, if the ISBN is 9781593275846 the URL would look like, http://localhost:3000/book/9781593275846:

app.get('/book/:isbn', (req, res) => {
    // Reading isbn from the URL
    const isbn = req.params.isbn;
});

Here, we're introduced to parameterized URLs. Since the ISBN depends on the book, there's potentially an infinite number of endpoints here. By adding a colon (:) to the path, we can define a variable, mapped to the variable isbn. So, if a user visits localhost:3000/book/5 the isbn parameter will be 5.

You can accept more than one parameter in your URL if it makes sense in your scenario. For example /image/:width/:height, and then you can get those parameters using req.params.width and req.params.height.

Now, using our endpoint, we can retrieve a single book:

app.get('/book/:isbn', (req, res) => {
    // Reading isbn from the URL
    const isbn = req.params.isbn;

    // Searching books for the isbn
    for (let book of books) {
        if (book.isbn === isbn) {
            res.json(book);
            return;
        }
    }

    // Sending 404 when not found something is a good practice
    res.status(404).send('Book not found');
});

Again restart the server, add a new book, and open localhost/3000/{your_isbn} and the application will return the book's information.

Deleting Books

When deleting entities, we typically delete them one by one to avoid big accidental data loss. To delete items, we use the HTTP DELETE method and specify a book using its ISBN number, just like how we retrieved it:

app.delete('/book/:isbn', (req, res) => {
    // Reading isbn from the URL
    const isbn = req.params.isbn;

    // Remove item from the books array
    books = books.filter(i => {
        if (i.isbn !== isbn) {
            return true;
        }
        return false;
    });

    res.send('Book is deleted');
});

We are using the app.delete method to accept DELETE requests. We have also used the array filter method to filter out the book with the relevant ISBN to remove it from the array.

Now let's implement the deleteBook method in the book-list.js file:

const deleteBook = (isbn) => {
    const xhttp = new XMLHttpRequest();

    xhttp.open("DELETE", `http://localhost:3000/book/${isbn}`, false);
    xhttp.send();

    // Reloading the page
    location.reload();
}

In this method, we are sending the delete request when the button is pressed and reloading the page to display the changes.

Editing Books

Very similar to deleting entities, updating them requires us to snatch a specific one, based on the ISBN and then send either a POST or PUT HTTP call with the new information.

Let's go back to our book-api.js file:

app.post('/book/:isbn', (req, res) => {
    // Reading isbn from the URL
    const isbn = req.params.isbn;
    const newBook = req.body;

    // Remove item from the books array
    for (let i = 0; i < books.length; i++) {
        let book = books[i]
        if (book.isbn === isbn) {
            books[i] = newBook;
        }
    }

    res.send('Book is edited');
});

Upon sending a POST request, aimed at a specific ISBN, the adequate book is updated with new information.

Since we have already created the edit modal, we can use the setEditModal method to gather information about the book when the "Edit" button is clicked.

We will also set the form's action parameter with the clicked book's URL to send the request:

const setEditModal = (isbn) => {
    // Get information about the book using isbn
    const xhttp = new XMLHttpRequest();

    xhttp.open("GET", `http://localhost:3000/book/${isbn}`, false);
    xhttp.send();

    const book = JSON.parse(xhttp.responseText);

    const {
        title,
        author,
        publisher,
        publish_date,
        numOfPages
    } = book;

    // Filling information about the book in the form inside the modal
    document.getElementById('isbn').value = isbn;
    document.getElementById('title').value = title;
    document.getElementById('author').value = author;
    document.getElementById('publisher').value = publisher;
    document.getElementById('publish_date').value = publish_date;
    document.getElementById('numOfPages').value = numOfPages;

    // Setting up the action url for the book
    document.getElementById('editForm').action = `http://localhost:3000/book/${isbn}`;
}

To verify if the update function works, edit a book. The form should be filled with the existing information about the book. Change something and click "Submit" after which you should see a "Book is edited" message.

Conclusion

That's how easy it is to build a REST API using Node.js and Express. You can visit the official Express documentation to learn more about the framework if you are interested.

Also, the code that I have provided is just for the sake of the tutorial, you should never use it in a production environment. Make sure you validate data and follow best practices when you write code for production.

As usual, the source code of this project can be found on GitHub.

Last Updated: August 31st, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

David LandupAuthor

Entrepreneur, Software and Machine Learning Engineer, with a deep fascination towards the application of Computation and Deep Learning in Life Sciences (Bioinformatics, Drug Discovery, Genomics), Neuroscience (Computational Neuroscience), robotics and BCIs.

Great passion for accessible education and promotion of reason, science, humanism, and progress.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms