Creating a simple REST API with Node and Hapi in 10 min


api hapi nodejs rest Tutorials

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.

Getting started

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

Set up the project

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();

Adding data

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.

Getting list of books

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.

Creating a new book

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
}]

Updating an existing book

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
}]

Deleting an existing book

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"
  }
]

Full example

'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();

Conclusion

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