Build RESTful API Using Node and Hapi

26 / May / 2015 by Kashish Gupta 12 comments

With the increasing popularity of Hapi in the Node community, it is a good option to build API’s using this framework. Let’s understand the Hapi framework and how easily we can define routes to build Hapi RESTful API for production ready environment. We will also be using some of the Hapi plugin’s for ease of API’s validation and to test routes.

Let’s look at the API we want to build and what it can do.

Our Application

We are going to build an API that will:

  • Handle CRUD for an item ( we’re are going to use `user` )
  • Have a standard URL ( “http://demo.com/api/user” and “http://demo.com/api/user/:id” )
  • Use the proper HTTP verbs to make it RESTful ( GET, POST, PUT, and DELETE )
  • Return JSON data
  • Log all request to the console

All of this is pretty standard for RESTful API’s. Feel free to switch out ‘user’ with anything that you want to build for the application ( cart, ship, company, etc ).

Make sure you have Node installed and let’s get to it!

GitHub Source Code Link

Getting Started

Let’s look at all the files we will need to create our API. We will need to define our Node packages, start our server using Hapi, define our model, register our plugin’s, declare our routes using Hapi, and last but not least, document and dry run API using Hapi Swagger.

Here is our file structure. When moving to a production or larger application, you’ll want to separate things out into a good structure (like having your routes in their own file).

    - models/
    ---- user.js        // our user model
    - node_modules/     // created by npm. holds our dependencies/packages
    - package.json      // define all our node app and dependencies
    - app.js            // configure our application and create routes

Defining our Node Packages (package.json)

Like our other Node projects, we will define the modules we need in package.json. Go ahead and create that file with these dependencies.

// package.json
{
    "name": "RESTful-HAPI",
    "version": "0.0.1",
    "main": "app.js",
    "dependencies": {
        "good-console": "^5.0.0",
        "hapi": "^8.5.1",
        "hapi-swagger": "0.7.3",
        "joi": "^6.4.2",
        "mongoose": "^4.0.3"
    }
}

What do these packages do? hapi is the Node framework. mongoose is the ORM we will use to communicate with our MongoDB database. good-console will let us see the URL’s logged on server console. joi is hapi plugin use to validate request params and payload data. hapi-swagger is Hapi plugin use to test API’s URL with request and valid response.

Installing our Node Packages

This might be the easiest step. Go into the command line in the root of your application and type:

$ npm install

npm will now pull in all the packages defined into a node_modules folder in our project.

npm is Node’s package manager that will bring in all the packages we defined in package.json. Now that we have our packages, let’s go ahead and use them to set up our API.

We’ll be looking to our app.js file to setup our app since that’s the main file we declared in package.json.

Setting up our server ( app.js )

Node will look here when starting the application so that it will know how we want to configure our application and API.

We will start with the user essentials necessary to start up our application.

// ================ Base Setup ========================
// Include Hapi package
var Hapi = require('hapi');

// Create Server Object
var server = new Hapi.Server();

// Define PORT number
server.connection({port: 7002});

// =============== Routes for our API =======================
// Define GET route
server.route({
    method: 'GET',      // Methods Type
    path: '/api/user',  // Url
    handler: function (request, reply) { //Action

        // Response JSON object
        reply({
            statusCode: 200,
            message: 'Getting All User Data',
            data: [
                {
                    name:'Kashish',
                    age:24
                },
                {
                    name:'Shubham',
                    age:21
                },
                {
                    name:'Jasmine',
                    age:24
                }
            ]
        });
    }
});

// =============== Start our Server =======================
// Lets start the server
server.start(function () {
    console.log('Server running at:', server.info.uri);
});

Base Setup In our base setup, we pull in all the packages we pulled in using npm. We’ll grab hapi, define our server object, call the connection method and pass the configuration where we set our port ( port: 7002).

Routes for Our API This section will hold all of our routes. The structure for using the Hapi Router. Let us call the route method and pass the route configuration object which holds the `method type`( GET ), `path`( /api/user ), `handler`to do all the action work.

Start our Server We’ll have our hapi app listen to the port we defined earlier (that is 7002). Then our application will be live and we will be able to test it.

We can test our application using POSTMAN plugin of browser. But what if application itself gives you the ability to test your routes with well documented way using Hapi Swagger plugin. Let’s see how easy it is to register any hapi plugin.

Register your plugin

`register` method is used to register a plugin. Hapi register method only accepts a configurable object.

// ================ Base Setup ========================
// Include Hapi package
var Hapi = require('hapi');

// Create Server Object
var server = new Hapi.Server();

// Define PORT number
server.connection({port: 7002});

// Register Swagger Plugin ( Use for documentation and testing purpose )
server.register({
    register: require('hapi-swagger'),
    options: {
        apiVersion: "0.0.1"
    }
}, function (err) {
    if (err) {
        server.log(['error'], 'hapi-swagger load error: ' + err)
    } else {
        server.log(['start'], 'hapi-swagger interface loaded')
    }
});

// =============== Routes for our API =======================
// Define GET route
server.route({
    method: 'GET',      // Methods Type
    path: '/api/user',  // Url
    config: {
        // Include this API in swagger documentation
        tags: ['api'],
        description: 'Get All User data',
        notes: 'Get All User data'
    },
    handler: function (request, reply) { //Action

        // Response JSON object
        reply({
            statusCode: 200,
            message: 'Getting All User Data',
            data: [
                {
                    name:'Kashish',
                    age:24
                },
                {
                    name:'Shubham',
                    age:21
                }
            ]
        });
    }
});

// =============== Start our Server =======================
// Lets start the server
server.start(function () {
    console.log('Server running at:', server.info.uri);
});

register method first argument accept { register` and `options` } fields are mandatory fields which further accept the configuration and second argument accept callback method which has one argument ( error ) to confirm that the plugin is successfully loaded.

Enable Swagger Testing / Documentation For Routes

You just have to add `tags:[‘api’]` property into `config` object and other two (description, notes) are optional fields.

Starting Our Server and Testing

Let’s make sure that everything is working up to this point. We will start our Node app and then send a request to the one route we defined to make sure we get a response.

Let’s start our server. From the command line, type:

$ node app.js

You should see your Node app start up and Hapi will create a server.

Runing Server

Now that we know our application is up and running, let’s test it. You can checkout on http://localhost:7002/api/user

Testing our API Using Swagger

Swagger will help us test our API. It will basically send HTTP requests to a URL of our choice. We can even pass in parameters (which we will soon) and authentication (which we won’t need for this tutorial).

Open up Swagger ( http://localhost:7002/documentation ) and let’s walk through how to use it.

swagger

All you have to do is click on api/user and then click the button ‘Try It out!’.

Now we know we can serve information to requests. Let’s wire up our database so we can start performing CRUD operations on some user.

Database and User Model

We’ll keep this short and crisp so that we can get to the fun part of building the API routes. All we need to do is create a MongoDB database and have our application connect to it. We will also need to create a user mongoose model so we can use mongoose to interact with our database.

Creating our database and Connecting

We will be using a local database. You can definitely use some other providers like Modulus and use it online or use the awesome Mongolab. All you really need is a URI like below so that your application can connect.

Once you have your database created and have the URI to connect to, add it to the application. In app.js in the Base Setup section, let’s add below lines. Ensure that you have mongoDB installed locally if you use local database.

var mongoose   = require('mongoose');
mongoose.connect('mongodb://localhost/restdemo'); // connect to local database

That will grab the mongoose package and connect to our local database. Now that we are connected to our database, let’s create a mongoose model to handle our users.

User Model ( models/user.js )

Since the model won’t be the focus of this tutorial, we’ll just create a model and provide our users with a name and age field. That’s it. Let’s create that file and define the model.

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var UserSchema = new Schema({
    name: String,
    age: Number
});

module.exports = mongoose.model('User', UserSchema, 'User');

With that file created, let’s pull it into our app.js so that we can use it within our application. We’ll add one more line to that file.

// ================ Base Setup ========================
// Include Hapi package
var Hapi = require('hapi');

// Create Server Object
var server = new Hapi.Server();

// Include Mongoose ORM to connect with database
var mongoose = require('mongoose');

// Making connection with `restdemo` database in your local machine
mongoose.connect('mongodb://localhost/restdemo');

// Importing `user` model from `models/user.js` file
var UserModel = require('./models/user');

// Define PORT number
server.connection({port: 7002});

// Register Swagger Plugin ( Use for documentation and testing purpose )
server.register({
    register: require('hapi-swagger'),
    options: {
        apiVersion: "0.0.1"
    }
}, function (err) {
    if (err) {
        server.log(['error'], 'hapi-swagger load error: ' + err)
    } else {
        server.log(['start'], 'hapi-swagger interface loaded')
    }
});

// =============== Routes for our API =======================
// Define GET route
server.route({
    method: 'GET',      // Methods Type
    path: '/api/user',  // Url
    config: {
        // Include this API in swagger documentation
        tags: ['api'],
        description: 'Get All User data',
        notes: 'Get All User data'
    },
    handler: function (request, reply) { //Action

        // Response JSON object
        reply({
            statusCode: 200,
            message: 'Getting All User Data',
            data: [
                {
                    name:'Kashish',
                    age:24
                },
                {
                    name:'Shubham',
                    age:21
                }
            ]
        });
    }
});

// =============== Start our Server =======================
// Lets start the server
server.start(function () {
    console.log('Server running at:', server.info.uri);
});

Now our entire application is ready and wired up so we can start building out our routes. These routes will define our API and the main reason why this tutorial exists.

Hapi Routes

We will use Hapi route method to handle all of our routes. Here is an overview of the routes we will require, what they will do, and the HTTP Verb used to access it.

Route HTTP Verb Description
/api/user GET Get all the users.
/api/user POST Create a new user.
/api/user/{id} GET Get a single user.
/api/user/{id} PUT Update a user with new info.
/api/user/{id} DELETE Delete a user.

This will cover the basic routes needed for an API. This also maintains a good format where it stores the actions needed to execute (GET, POST, PUT, and DELETE) as HTTP verbs.

Creating the Basic Routes

We will now create the routes to handle getting all the users and creating a user. This will both be handled using the /api/user route. We’ll look at creating a user first so that we have users to work with.

Create a User ( POST /api/user )

We will add the new route to handle POST and then test it using Hapi Swagger.

server.route({
    method: 'POST',
    path: '/api/user',
    config: {
        // "tags" enable swagger to document API
        tags: ['api'],
        description: 'Save user data',
        notes: 'Save user data',
        // We use Joi plugin to validate request
        validate: {
            payload: {
                // Both name and age are required fields
                name: Joi.string().required(),
                age: Joi.number().required()
            }
        }
    },
    handler: function (request, reply) {

        // Create mongodb user object to save it into database
        var user = new UserModel(request.payload);

        // Call save methods to save data into database
        // and pass callback methods to handle error
        user.save(function (error) {
            if (error) {
                reply({
                    statusCode: 503,
                    message: error
                });
            } else {
                reply({
                    statusCode: 201,
                    message: 'User Saved Successfully'
                });
            }
        });
    }
});

Now we have created the POST route for our application. We will use Hapi  server.route({}) to handle multiple routes for the same URI. We are able to handle all the requests that end in /user.

Let’s look at Swagger now to create our user.

rsz_1post_before_send

Notice that we are preparing our user JSON to send as POST request and “name” and “age” are required field here. Let’s see the response of the above request we made.

We can easily see the response we have just got with statusCode:201 and message:”User Saved Successfully”

Get All the Users ( GET /api/user )

Now we have a user in our database, so why not to get all the users from the database by defining GET route in our application to fetch all users.

// Fetching all users data
server.route({
    method: 'GET',
    path: '/api/user',
    config: {
        // Include this API in swagger documentation
        tags: ['api'],
        description: 'Get All User data',
        notes: 'Get All User data'
    },
    handler: function (request, reply) {
        //Fetch all data from mongodb User Collection
        UserModel.find({}, function (error, data) {
            if (error) {
                reply({
                    statusCode: 503,
                    message: 'Failed to get data',
                    data: error
                });
            } else {
                reply({
                    statusCode: 200,
                    message: 'User Data Successfully Fetched',
                    data: data
                });
            }
        });
    }
});

Now we have created the GET route in our application to fetch all record. Let’s check this on Swagger.

rsz_get-all-user-request

We can see that all the user data get fetched with database “_id” keys. Why not get single user data by just passing unique key as shown above with “_id”?

Creating Route for A Single Item

We’ve handled the group for routes ending in /user. Let’s now handle the routes for when we pass in a parameter like a user’s id.

The things we’ll want to do for this route, which will end in/user/{id} will be:

  • Get a single user.
  • Update a user’s info.
  • Delete a user.

Getting a single User ( GET /api/user/{id} )

We’ll add another server.route({}) to handle all requests that have a {id} attached to them.

server.route({
    method: 'GET',
    //Getting data for particular user "/api/user/1212313123"
    path: '/api/user/{id}',
    config: {
        tags: ['api'],
        description: 'Get specific user data',
        notes: 'Get specific user data',
        validate: {
            // Id is required field
            params: {
                id: Joi.string().required()
            }
        }
    },
    handler: function (request, reply) {

        //Finding user for particular userID
        UserModel.find({_id: request.params.id}, function (error, data) {
            if (error) {
                reply({
                    statusCode: 503,
                    message: 'Failed to get data',
                    data: error
                });
            } else {
                if (data.length === 0) {
                    reply({
                        statusCode: 200,
                        message: 'User Not Found',
                        data: data
                    });
                } else {
                    reply({
                        statusCode: 200,
                        message: 'User Data Successfully Fetched',
                        data: data
                    });
                }
            }
        });
    }
});

From our call to get all the user’s, we can see the long id of one of our users. Let’s grab that id and test getting that single user in Swagger. Let see how a request looks in Swagger.

GET-Single-User-data-request

Now when we click on Try it out! button we get the response with single user data.

We can grab one user from our API now! Let’s look at updating that user’s name. Let’s say he forgot to mention his last name so we’ll rename him from Shubham to Shubham Gupta.

Updating a user’s info ( PUT /api/user )

server.route({
    method: 'PUT',
    path: '/api/user/{id}',
    config: {
        // Swagger documentation fields tags, description, note
        tags: ['api'],
        description: 'Update specific user data',
        notes: 'Update specific user data',

        // Joi api validation
        validate: {
            params: {
                //`id` is required field and can only accept string data
                id: Joi.string().required()
            },
            payload: {
                name: Joi.string(),
                age: Joi.number()
            }
        }
    },
    handler: function (request, reply) {

        // `findOneAndUpdate` is a mongoose modal methods to update a particular record.
        UserModel.findOneAndUpdate({_id: request.params.id}, request.payload, function (error, data) {
            if (error) {
                reply({
                    statusCode: 503,
                    message: 'Failed to get data',
                    data: error
                });
            } else {
                reply({
                    statusCode: 200,
                    message: 'User Updated Successfully',
                    data: data
                });
            }
        });

    }
});

Now let’s see how a request looks at Swagger UI.

PUT-Update-User-Data-Request

A response will return old data with status code as true, which means data has been updated successfully.

We can also use the GET /api/user call we used earlier to see that his name is changed.

Showing-Updated-name

Deleting a User ( DELETE /api/user )

When someone requests that a user is deleted, all they have to do is send a DELETE to /api/user/{id}

Let’s add the code for deleting user.

server.route({
    method: 'DELETE',
    path: '/api/user/{id}',
    config: {
        tags: ['api'],
        description: 'Remove specific user data',
        notes: 'Remove specific user data',
        validate: {
            params: {
                id: Joi.string().required()
            }
        }
    },
    handler: function (request, reply) {

        // `findOneAndRemove` is a mongoose methods to remove a particular record into database.
        UserModel.findOneAndRemove({_id: request.params.id}, function (error) {
            if (error) {
                reply({
                    statusCode: 503,
                    message: 'Error in removing User',
                    data: error
                });
            } else {
                reply({
                    statusCode: 200,
                    message: 'User Deleted Successfully'
                });
            }
        });

    }
});

Now when we send a request to our API using DELETE with the _id, we’ll delete our user from existence. Let delete Shubham Gupta user from collection.

DELETE-User-Removed

When we try to get Shubham Gupta with same _id from user collection, there will be nothing left and showing User not found.

Conclusion

We now have the means to handle CRUD on a specific resource (our beloved users) through our own API. Using the techniques above should be a good foundation to move into building larger and more robust APIs.

This has been a quick look at creating a Node API using HapiJs. There are many more things you can do with your own APIs. You can add authentication, create better error messages, add different sections so you’re not just working with users.

Hope this help. 🙂

FOUND THIS USEFUL? SHARE IT

comments (12)

  1. Ramakrishna S

    Hello Kashish Gupta, I really appreciate your time for putting detail information about Swagger integration. I was wondering is there any option to export as json using the swagger plugin? if not, Could you please suggest any other module which will export as JSON. Let me exlain you my requiremnt. Basically we already have legacy API. With the help of swagger plugin(as mentioned in this blog), i have documentation. But we would like to export as json and deploy on different server. Thank You.

    Reply
  2. Abhishek

    Hey, thanks for the awesome article, I am working on something that requires the use of mysql with hapi, what needs to be changed when we have mysql?

    Reply
  3. Justin Headley

    Very nice explanation and walk through of the basic elements, thanks! If you’re interested I wrote a hapi plugin that uses mongoose to auto-generate RESTful API endpoints based on model configurations, called “rest-hapi”. It also generates swagger docs for every endpoint.

    github.com/JKHeadley/rest-hapi

    I also used rest-hapi to create a user-system boilerplate hapi resource:

    github.com/JKHeadley/appy

    Please let me know what you think! 🙂

    Reply
  4. Raj

    The above tutorial will work only if you include good module of version 6.0 and install mongodb on OS

    {
    “name”: “RESTful-HAPI”,
    “version”: “0.0.1”,
    “dependencies”: {
    “good”: “^6.0.0”,
    “good-console”: “^5.0.0”,
    “hapi”: “^8.5.1”,
    “hapi-swagger”: “0.7.3”,
    “joi”: “^6.4.2”,
    “mongoose”: “^4.0.3”
    }
    }

    Reply
  5. freeMan

    hi Kashish Gupta ,
    Thanks for your tutorial.
    I’m a beginner, pls help me to please help me to build file and folder structure of project.
    I’m following this tutorial: gist.github.com/agendor/9922151
    But this is old Hapi version.
    ————-
    My project have some components as : middleway, auth, validate, error handler, route, model, config, socket io .

    Thansk and Regards,

    Reply
  6. Khaled

    Hello,
    Thanks for such informative article
    I faced some issues with dependencies,
    appreciate it if you can share the OS used and node/npm versions.

    I tried to run it using
    node-v0.10.45-x64.msi
    node-v4.3.1-x64.msi
    on windows
    and node 4.4.3 on ubuntu

    node-v4.4.4-x64.msi on windows 7 reported the following after “npm -install”

    npm ERR! Windows_NT 6.1.7601
    npm ERR! argv “C:\\Program Files\\nodejs\\node.exe” “C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js” “install”
    npm ERR! node v4.4.4
    npm ERR! npm v2.15.1
    npm ERR! code EPEERINVALID

    npm ERR! peerinvalid The package hapi@8.8.1 does not satisfy its siblings’ peerDependencies requirements!
    npm ERR! peerinvalid Peer hapi-swagger@0.7.3 wants hapi@>= 8.x.x =10.x.x

    I tried it on ubuntu 14.04
    running node v4.4.3
    and it showed:

    npm ERR! Linux 3.13.0-85-generic
    npm ERR! argv “/usr/bin/nodejs” “/usr/bin/npm” “install”
    npm ERR! node v4.4.3
    npm ERR! npm v2.15.1
    npm ERR! code EPEERINVALID

    npm ERR! peerinvalid The package hapi@8.8.1 does not satisfy its siblings’ peerDependencies requirements!
    npm ERR! peerinvalid Peer hapi-swagger@0.7.3 wants hapi@>= 8.x.x =10.x.x

    in both cases I was using the package.json you mentioned

    {
    “name”: “RESTful-HAPI”,
    “version”: “0.0.1”,
    “dependencies”: {
    “good-console”: “^5.0.0”,
    “hapi”: “^8.5.1”,
    “hapi-swagger”: “0.7.3”,
    “joi”: “^6.4.2”,
    “mongoose”: “^4.0.3”
    }
    }

    Reply
  7. Anton S Rodionov

    In newset version dont work, need replace // Register Swagger Plugin ( Use for documentation and testing purpose )
    server.register({
    register: require(‘hapi-swagger’),
    options: {
    apiVersion: “0.0.1”
    }
    }, function (err) {
    if (err) {
    server.log([‘error’], ‘hapi-swagger load error: ‘ + err)
    } else {
    server.log([‘start’], ‘hapi-swagger interface loaded’)
    }
    });
    to server.register([
    inert,
    vision,
    hapiswg,
    ], function (err) {
    if (err) {
    server.log([‘error’], ‘hapi-swagger load error: ‘ + err)
    } else {
    server.log([‘start’], ‘hapi-swagger interface loaded’)
    }
    }
    );

    Reply
  8. Jeet Patel

    Hi Kashish,
    How can i pass my monogo DB cursor result to the HTML template?
    I want to iterate the cursor result in HTML template. so Please help me.

    Thanks & Regards
    Jeet Patel

    Reply
      1. Rajesh kushwaha

        Hi kaisa,
        Thanks for giving good example of rest API with mongo database.
        Can you help to me regarding rest API with Cassandra database .I want to do same setup with Cassandra database.i you have any tutorial related to Cassandra then share that URL.

        Thank
        Rajesh

        Reply

Leave a Reply

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