In this tutorial we will create a simple REST API using the Node and Hapi framework. We will create a simple server with API which returns the books data and allow us to create, update and delete books.
To follow this tutorial you should have the basic fundamentals of using command line. All the following was done with the next requirements: Node v9.10.0, Hapi v17.8.1
Create a new folder and initialize a new project using the next command:
npm init
Then install Hapi framework with plugins, FS module to read/write data and Util module:
npm install --save hapi@17.x.x fs util
Now we can create the main file for our server. Let’s call it index.js
and add the following code of very basic server:
'use strict';
const Hapi = require('hapi');
const fs = require('fs');
const util = require('util');
// Convert fs.readFile, fs.writeFile into Promise version of same
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
const init = async () => {
await server.start();
console.log(`Server running at: ${server.info.uri}`);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
In our app we will use the JSON file as a main data store. Create a new books.json
file in project folder and paste the next data:
[
{
"id": 1,
"author": "Suzanne Collins",
"title": "The Hunger Games (The Hunger Games, #1)"
},
{
"id": 2,
"author": "William Golding",
"title": "Lord of the ÏFlies"
},
{
"id": 3,
"author": "Harper Lee",
"title": "To Kill a Mockingbird"
}]
We will make manipulations with this file on server, when the client makes the GET, POST, PUT, DELETE requests.
Let’s create our first REST API endpoint to get the list of books. Add the following code in index.js
file:
server.route({
method: 'GET',
path: '/books',
options: {
handler: async (request, h) => {
const books = await readFile('./books.json', 'utf8');
return h.response(JSON.parse(books));
}
}
});
In the “handler” method we read the data from books.json
file using the “readFile” function, then parse it using “JSON.parse” and send using the “.response” method. We need to parse this data, because the books variable it’s a string (JSON) and the server will send it as HTML text, but if “.response” method will take the pure javascript object, it sets all headers correctly. In this case client will understand that this is JSON.
When you access defined API route using_ GET http://localhost:3000/books _you will get the list of all books frombooks.json
file.
To create a new book we need to define a POST /books _route:_
server.route({
method: 'POST',
path: '/books',
options: {
handler: async (request, h) => {
const book = JSON.parse(request.payload);
let books = await readFile('./books.json', 'utf8');
books = JSON.parse(books);
// setting id
book.id = books.length + 1;
books.push(book);
await writeFile('./books.json', JSON.stringify(books, null, 2), 'utf8');
return h.response(books).code(200);
}
}
});
Inside handler, we setting the received data from client to book variable. Then reading the books data, setting a simple identifier and push a new book to books array. After all these actions, we rewriting the books.json
and send an updated array of books to the client.
For example, if we make a POST request to /books endpoint with the next data:
{
"author": "Roman Akhromieiev",
"title": "Hapi Ebook"
}
Server will returns the next result:
[
{
"id": 1,
"author": "Suzanne Collins",
"title": "The Hunger Games (The Hunger Games, #1)"
},
{
"id": 2,
"author": "William Golding",
"title": "Lord of the ÏFlies"
},
{
"id": 3,
"author": "Harper Lee",
"title": "To Kill a Mockingbird"
},
{
"author": "Roman Akhromieiev",
"title": "Hapi Ebook",
"id": 4
}]
In this section we will update an existing book by ID. We will send the book ID as a query params and new book data in request payload. Add the following code toindex.js
:
server.route({
method: 'PUT',
path: '/books/{id}',
options: {
handler: async (request, h) => {
const updBook = JSON.parse(request.payload);
const id = request.params.id;
let books = await readFile('./books.json', 'utf8');
books = JSON.parse(books);
// finding book by id and rewriting
books.forEach((book) => {
if (book.id == id) {
book.title = updBook.title;
book.author = updBook.author;
}
});
await writeFile('./books.json', JSON.stringify(books, null, 2), 'utf8');
return h.response(books).code(200);
}
}
});
The _request.params.id _it’s ID from path. We using it to look an existing book in the books array. When we find it, we update it.
For example, if we make a PUT request to /books/1 endpoint with the next data:
{"author":"Roman Akhromieiev","title":"React Ebook"}
Server will produce the next result:
[
{
"id": 1,
"author": "Roman Akhromieiev",
"title": "React Ebook"
},
{
"id": 2,
"author": "William Golding",
"title": "Lord of the ÏFlies"
},
{
"id": 3,
"author": "Harper Lee",
"title": "To Kill a Mockingbird"
},
{
"author": "Roman Akhromieiev",
"title": "Hapi Ebook",
"id": 4
}]
To remove an existing book from our books.json
file, we will use the similar logic as we used in previous section. Let’s add the DELETE /books route to our server:
server.route({
method: 'DELETE',
path: '/books/{id}',
options: {
handler: async (request, h) => {
const updBook = JSON.parse(request.payload);
const id = request.params.id;
let books = await readFile('./books.json', 'utf8');
books = JSON.parse(books);
// rewriting the books array
books = books.filter(book => book.id != id);
await writeFile('./books.json', JSON.stringify(books, null, 2), 'utf8');
return h.response(books).code(200);
}
}
});
We filtering the existing array of books and exclude the book, which id we passed to our server. For example, if we make a DELETE request to /books/2 endpoint, we get the following result:
[
{
"id": 1,
"author": "Suzanne Collins",
"title": "The Hunger Games (The Hunger Games, #1)"
},
{
"id": 3,
"author": "Harper Lee",
"title": "To Kill a Mockingbird"
}
]
'use strict';
const Hapi = require('hapi');
const fs = require('fs');
const util = require('util');
// Convert fs.readFile, fs.writeFile into Promise version of same
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
server.route({
method: 'GET',
path: '/books',
options: {
handler: async (request, h) => {
const books = await readFile('./books.json', 'utf8');
return h.response(JSON.parse(books));
}
}
});
server.route({
method: 'POST',
path: '/books',
options: {
handler: async (request, h) => {
const book = JSON.parse(request.payload);
let books = await readFile('./books.json', 'utf8');
books = JSON.parse(books);
// setting id
book.id = books.length + 1;
books.push(book);
await writeFile('./books.json', JSON.stringify(books, null, 2), 'utf8');
return h.response(books).code(200);
}
}
});
server.route({
method: 'PUT',
path: '/books/{id}',
options: {
handler: async (request, h) => {
const updBook = JSON.parse(request.payload);
const id = request.params.id;
let books = await readFile('./books.json', 'utf8');
books = JSON.parse(books);
// finding book by id and rewriting
books.forEach((book) => {
if (book.id == id) {
book.title = updBook.title;
book.author = updBook.author;
}
});
await writeFile('./books.json', JSON.stringify(books, null, 2), 'utf8');
return h.response(books).code(200);
}
}
});
server.route({
method: 'DELETE',
path: '/books/{id}',
options: {
handler: async (request, h) => {
const updBook = JSON.parse(request.payload);
const id = request.params.id;
let books = await readFile('./books.json', 'utf8');
books = JSON.parse(books);
// rewriting the books array
books = books.filter(book => book.id != id);
await writeFile('./books.json', JSON.stringify(books, null, 2), 'utf8');
return h.response(books).code(200);
}
}
});
const init = async () => {
await server.start();
console.log(`Server running at: ${server.info.uri}`);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
As you see, you can quickly create a basic REST API using the Hapi framework for 10 minutes. With this simple tutorial you can start to discover more complex topics like querying data from DB inside route handlers and sending to client, creating a separate folders for API routes, how to work with parameters or how to send files in your API.
Learning Hapi.js? Buy my Hapi.js Handbook🔥
comments powered by Disqus