Monday, 14 October, 2019 UTC


Summary

Introduction

In this tutorial, we are going to build a REST API to manage books with Node.js and Express. To get started with it, I assume that you have Node.js installed, you have some experience in JavaScript, and some basic knowledge of HTML and Bootstrap.
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?

REpresentation State Transfer (REST) is a set of methods in which HTTP clients can request information from the server via the HTTP protocol. We will get a little bit deeper about HTTP methods in later this article.
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).
REST APIs use different HTTP request methods, corresponding to the previously mentioned actions, to retrieve and manipulate data.

What is Express?

ExpressJS is one of the most popular HTTP server libraries for Node.js, which ships with very basic functionalities. However, it's highly customizable using something called "middleware". ExpressJS is very easy to use, install, and learn.
Even though we are only going to build a REST API in this article, the ExpressJS framework is not limited to just that - you can do all sorts of things like hosting static files, performing server-side rendering, or even use it as a proxy server.

HTTP Request Types

There are a few types of HTTP methods that we need to know 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

Now we have a basic understanding of what is REST and basic HTTP methods. Let's create a simple app to store information about books. In this app, we will store information about ISBN of the book, title, author, published date, publisher and number of pages.
I will be using this dataset from GitHub to get some sample details about books.
So let's get started building our app then.

Setting Up the Project

First, let's initialize a new Node.js project:
$ npm init
Fill the requested information to your requirements.
Now we can install the web framework by running:
$ npm install --save express

Creating a Simple Endpoint

Now, let's start building a simple "Hello World" app. When we visit our app in the browser, we'll be greeted with a simple message.
First, create a file called hello-world.js and import the Express framework:
const express = require('express');
Now, let's create the Express app:
const app = express();
And set our port:
const port = 3000;
Now, we can create a simple GET endpoint. When a user hits the endpoint, the message "Hello World, from express" will appear. 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!

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. There are third party extensions like morgan for logging, multer for handling file uploads, etc.
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
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 the API

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 an 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 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 parametrized 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 "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.