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">×</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.