In this tutorial, we will see how we can build REST APIs in Node.js using Hapi.js as our framework.

Hapi.js is a rich Node.js framework for building applications and services. It was started by Eran Hammer in 2011, since then it is slowly but steadily growing popular and has been compared to the likes of ExpressJS and SailsJS increasing amount of times.

Hapi works on configuration rather than code, this enables developers to easily setup app infrastructure and focus on application logic. It also makes applications more scalable. Unlike ExpressJS which is designed to be a slim web framework, Hapi.js has a lot of baked in features and some great plugins which solves much of the common problems in Node.js applications. Validation using joi and logging are a few examples. The official documentation is pretty solid and in depth as well.

Setting up new Node Project

Create a directory for your project, navigate into it using cli tool and run

npm init

Answer the questions as required. This will create a package.json file for you. Next install hapi.js


npm install --save hapi

This will install hapi.js and save it to package.json as a dependency.

ES6

At a few places, we will be sing ES6 syntax, so you’ll see const instead var and arrow functions instead of standard callback. Make sure you know about them. Also, make sure you have node 4.4.6 or greater installed.

Creating a server with Hapi

Create a file name app.js and add the below code.

app.js

'use strict';

const hapi = require('hapi');
const server = new hapi.Server();
server.connection({host:'localhost', port: 3000});

server.start((err) => {
    if(err) {
        throw err;
    }
    console.log(`Server running at: ${server.info.uri}`);
})

First, we are requiring hapi, next, we create a server object and configure our connection. Finally, we are starting our server using the start function and logging the details to the console.

To test it out you can run node app.js. Your node server should be up and running on http://localhost:3000.

DOWNLOAD

A brief

[adrotate banner=”2″]

Now that we have successfully setup the server, we can start building our APIs. Just to let you know, REST APIs follow a unique architectural style. In which the HTTP methods(GET, PUT, POST, DELETE) represent the action we will perform on our object.

The analogy goes as follows.

  • GET => Should retrieve all items from the entity
  • POST => Should add a new item to the entity
  • PUT => Updates the item based on unique id
  • DELETE => Deletes the item based on unique id
  • GET (with id) => retrieve specific item based on id from the entity

For this tutorial, we will be using a temporary array to store our data instead of connecting to a database.

Creating routes

Create a directory named routes and a file named routes.js within it.


mkdir routes
cd routes
touch routes.js

Let us first create our temporary data store.

routes/routes.js

'use strict';
module.exports = function() {
    var store = [
        {
            name: 'toy',
            price: '2'
        }
    ]

        return [//our routes goes here];
}();

Nothing fancy here, we have a simple array as store which will hold items with properties as name and price.

Now let’s go ahead and try to fetch all items from the stores.

routes/routes.js

'use strict';
module.exports = function() {
    var store = [
        {
            name: 'toy',
            price: '2'
        }
    ]

        return [
               {
            method: 'GET',
            path: '/store/',
            config : {
                handler: function(req, reply){
                    reply({'store':store, 'responseCode':0});
                }
            }
        }
        ];
}();

As you can see above the route configuration takes in three primary parameters. A method for the request (GET, POST, PUT, DELETE), a path and config.
The handler option is a method with two parameters, req contains all the details of the request and reply is used to respond to the request. For this request, we are returning the complete store data as a response.

There is one last thing left to make this work, adding the routes to the server object using the server.route method. Let’s head over to app.js and add the same.

app.js

'use strict';

const hapi = require('hapi');
const server = new hapi.Server();
var routes = require('./routes/routes.js'); //require routes
server.connection({host:'localhost', port: 3000});

server.route(routes); //add routes

server.start((err) => {
    if(err) {
        throw err;
    }
    console.log(`Server running at: ${server.info.uri}`);
})

First, we are requiring the routes and then adding the same to the server object as highlighted in the comments above.

Test it out

Start the node server again by running node app.js, and since this is a get request you can simply hit the URL. http://localhost:3000/store/ and see the result or you can use any REST client, I’ll be using POSTMAN, it’s easy to use and has loads of cool features. If you have followed correctly you should be seeing the below result.

Hapi.js GET request
Hapi.js GET request

Please Note: The URL should exactly match the path defined in the route configuration including the trailing slash.

Adding more operations

Now that we have got a taste how this works, let’s go ahead and add more operations on our store.

Get an item by id

Here we will try to fetch a single item based on the id passed. Since we are using an in memory array we will treat the position of the items in the array as the id.

routes/routes.js

'use strict';
module.exports = function() {
    var store = [
        {
            name: 'toy',
            price: '2'
        }
    ]

        return [
               {
            method: 'GET',
            path: '/store/',
            config : {
                handler: function(req, reply){
                    reply({'store':store, 'responseCode':0});
                }
            }
        },
                {
            method: 'GET',
            path: '/store/{id}',
            config : {
                handler: function(req, reply){
                    if(store.length <= req.params.id){
                        return reply({message:"product does not exists", responseCode: 1}).code(404);
                    }
                                        reply({'store':store[req.params.id], 'responseCode':0});
                                  }
                
                }
                 }
        ];
}();

Parameters can be passed into the url by placing them between curly braces {..}. These url parameters are accesseble in the handler method with the req.params object. For this operation we are first checking if the item exists, if not then we ar responding with a 404 http status else we are returning the correct item based on the id.

Hapi.js GET by id request
Hapi.js GET by id request

Add new objects to store (POST)

Let's try and add an operation which will allow us to add new items to our store. For this, we will use the POST method. Add the below object to your routes array.

routes/routes.js (Add Object)

{
            method: 'POST',
            path: '/store/',
            config : {
                handler: function(req, reply){
                    console.log(req.payload);
                    store.push({
                        name: req.payload.name,
                        price: req.payload.price
                    });
                    reply({message:'Successfully added the data', responseCode:0});
                }
                
            }
        }

The request payload can be accessed inside the handler method by using req.payload object.

Hapi.js POST request
Hapi.js POST request

Delete an item from store (DELETE)

Let's go ahead and add functionality to delete objects from our store.

routes/routes.js (Delete Object)

        {
            method: 'DELETE',
            path: '/store/{id}',
            config : {
                handler: function(req, reply){
                    if(store.length <= req.params.id){
                        return reply({message:"product does not exists", responseCode: 1}).code(404);
                    }
                    store.splice(req.params.id, 1);
                    reply({message:`Successfully deleted ${req.params.id}`, responseCode:0});
                }
                
            }
        }

We are taking the id as a URL param like we did in case of fetching an object. And then using the splice method we are removing the item based on the id.

Hapi.js DELETE request
Hapi.js DELETE request

Update an item in the store (PUT)

For updating or editing an item in the store we will use the PUT method over our store end-point.

routes/routes.js (Delete Object)

        {
            method: 'PUT',
            path: '/store/{id}',
            config : {
                handler: function(req, reply){
                    /** well, I'll leave this part on you **/
                }
                
            }
        }

You should be passing the id of the item to be updated as a URL param and along with it should come the payload which will overwrite the existing item with the new payload data.

Validating user inputs

One important aspect of any application that involves user inputs is the validation. Hapi.js provides us with configuration and an awesome plugin joi that makes this validation easier and hassle free.

Just require the plugin in your routes.js and you are ready to go.

var joi = require('joi');

 
We can add validations by specifying configuration inside the config object of each routes. See example below.

Validations in Hapi.js

{
            method: 'PUT',
            path: '/store/{id}',
            config : {
                handler: function(req, reply){
                    //.....
                },
                validate: {
                    payload : { //to validate payload
                        name: joi.string(),
                        price: joi.number()
                    },
                                        params: { //to validate url params
                                                id: joi.number().integer().min(1).max(100)
                                        },
                                        query: {
                                                //to validate query params
                                        }
                }
            }
        }

Final Files

So our final files after adding all the validations should look like this.

package.json

{
  "name": "rest-apis-nodejs-hapi",
  "version": "1.0.0",
  "description": "Rest APIs using Node.js and Hapi",
  "main": "app.js",
  "scripts": {
    "test": "npm test"
  },
  "author": "Rahil Shaikh",
  "license": "MIT",
  "dependencies": {
    "hapi": "14.2.0"
  }
}

app.js

'use strict';

const hapi = require('hapi');
const server = new hapi.Server();
var routes = require('./routes/routes.js');
server.connection({host:'localhost', port: 3000});

server.route(routes);

server.start((err) => {
    if(err) {
        throw err;
    }
    console.log(`Server running at: ${server.info.uri}`);
})

routes/routes.js


'use strict';

var joi = require('joi');
module.exports = function() {
    var store = [
        {
            name: 'toy',
            price: '2'
        }
    ];
    return [
        {
            method: 'GET',
            path: '/store/',
            config : {
                handler: function(req, reply){
                    reply({'store':store, 'responseCode':0});
                }
            }
        },

        {
            method: 'GET',
            path: '/store/{id}',
            config : {
                handler: function(req, reply){
                    if(store.length <= req.params.id){
                        return reply({message:"product does not exists", responseCode: 1}).code(404);
                    }
                    reply({'store':store[req.params.id], 'responseCode':0});
                },
                validate: {
                    params: {
                        id: joi.number()
                    }
                }
            }
        },

        {
            method: 'POST',
            path: '/store/',
            config : {
                handler: function(req, reply){
                    console.log(req.payload);
                    store.push({
                        name: req.payload.name,
                        price: req.payload.price
                    });
                    reply({message:'Successfully added the data', responseCode:0});
                },
                validate: {
                    payload : {
                        name: joi.string(),
                        price: joi.number()
                    }
                }
            }
        },

        {
            method: 'DELETE',
            path: '/store/{id}',
            config : {
                handler: function(req, reply){
                    if(store.length <= req.params.id){
                        return reply({message:"product does not exists", responseCode: 1}).code(404);
                    }
                    store.splice(req.params.id, 1);
                    reply({message:`Successfully deleted ${req.params.id}`, responseCode:0});
                },
                validate: {
                    params : {
                        id: joi.number()
                    }
                }
            }
        }
    ];
}();

Conclusion

Hapi.js seems to be a promising framework to setup our apps quickly and also make our node.js apps more robust and scalable. Hapi.js believes in configuration over code that way the only code you write for your application is the business logic for the handler methods and hence you can save time otherwise you would have spent in writing middlewares and plugins. In this tutorial, we covered how we can build REST APIs with Node.js and Hapi.js. In future, we intend to cover more tutorials on Hapi.js

Further Reading

5 Comments

  1. I kept getting 404 and I’m using your code in its entirety.

    error:
    sh: app.js: command not found

    I should mention I went to the package.json file and added

    “start”: “app.js”

    So that I could run npm start.

    1. Amazing work!
      Would probably write an article on it once I try it out.
      I also have a few question related to the demo app you’ve made. Will catch you on Github or email.

Leave a Reply

Your email address will not be published. Required fields are marked *