Building a RESTful API in Node using hapi.js 8.0

This is the first of a series of posts for building a node API using hapi. In this tutorial we'll dive into the basics of hapi.js. We'll be building a simple API to keep track of tasks.

tl;dr take a look at the code and demo.

Before we get started let's initialize npm in a directory:

$ mkdir hapijs-restful-api-example
$ cd hapijs-restful-api-example
$ touch index.js
$ npm init

and now install the hapi npm module

$ npm install hapi --save

Open the index.js we created and paste this code:

// Require the Hapi node module
var Hapi = require('hapi');
 
// Create a server instance
var server = new Hapi.Server();
 
// Create a connection which will listen on port 8000
server.connection({
    host: 'localhost',
    port: 8000
});
 
// Add a GET endpoint /hello
server.route({
    method: 'GET',
    path: '/hello',
    handler: function (request, reply) {
        reply('world');
    }
 });
 
// Start the server
server.start();

The code should be self explanatory, but you can see that we've created a single endpoint for our API.
The API will respond to GET requests on the /hello endpoint. And it should respond with the string world. Any requests not directed to this endpoint will result in a 404 response. So let's see it in action. In one terminal session start the server:

$ node index.js

And in another terminal session make a request to our API:

$ curl -XGET http://localhost:8000/hello

If you see a response of world you've created your first hapi API server, yay!

Meet hapi plugins

Plugins can do an array of things; They're plugins after all. For now we'll be using plugins to modularize the route we just created.
Let's create a routes folder containing our tasks routes.

$ mkdir routes
$ cd routes
$ touch tasks.js

Now open tasks.js and paste the following code:

// Options can be passed to plugins on registration
exports.register = function(server, options, next) {
    server.route([
        {
            method: 'GET',
            path: '/tasks',
            handler: function (request, reply) {
                reply('Do all the things!');
            }
        }
    ]);
 
    // Callback, completes the registration process
    next();
}
 
// Required for all plugins
// If this were a npm module, one could do this:
// exports.register.attributes = require('package.json')
exports.register.attributes = {
    name: 'tasks-route', // Must be unique
    version: '1.0.0'
};

This file will be responsible for all routes to the /tasks endpoint.

Now we'll need to update our index.js.
Replace its current contents with the following code:

var Hapi = require('hapi');
 
var server = new Hapi.Server();
 
server.connection({
    host: 'localhost',
    port: 8000
});
 
// Declare plugins
var plugins = [
    { register: require('./routes/tasks.js') }
];
 
// Register plugins, and start the server if none of them fail
server.register(plugins, function (err) {
    if (err) { throw err; }
 
    server.start(function () {
        server.log('info', 'Server running at: ' + server.info.uri);
    });
});

This configures our server with plugins.
Now start the server again $ node index.js and execute this in another terminal session:

$ curl -XGET http://localhost:8000/tasks

The server should respond with: Do all the things!

Now let's add a POST endpoint that would allow us to save a task.
Update the tasks.js file with the following contents:

exports.register = function(server, options, next) {
    // Our storage of tasks
    var tasks = [];
 
    server.route([
        {
            method: 'GET',
            path: '/tasks',
            handler: function (request, reply) {
                // Return the list of tasks
                reply(tasks);
            }
        },
        {
            method: 'POST',
            path: '/tasks',
            handler: function (request, reply) {
                // Get the task
                var task = request.payload.task;
                // Let's store the task
                var key = tasks.push(task);
                reply({key: key - 1, task: task});
            }
        }
    ]);
 
    next();
}
 
exports.register.attributes = {
    name: 'routes-tasks',
    version: '1.0.0'
};

If we start the server again we can now add our first task to the API:

$ curl -XPOST http://localhost:8000/tasks \
       -H 'Content-Type: application/json' \
       -d '{"task": "first task ever!"}'

The response to this request should be:

{"key":0,"task":"first task ever!"}

Awesome!

We are sending a JSON document to the API, so for it to parse it corrently we must send a Content-Type header along with the request.

Requesting all tasks will return an array containing all stored tasks.

$ curl -XGET http://localhost:8000/tasks
["first task ever!"]

So there you have it, you've learned how to create a very basic API using hapi.

Here's a live demo of the complete API. It contains features that I didn't cover in this post, like PUT/DELETE endpoints and input validation. Feel free to play around with it!

In follow up posts, I'll be talking about:

  • PUT/DELETE endpoints
  • Logging
  • Input validation
  • Separation of concerns
  • Testing
In the meantime feel free to checkout the repository that contains the final product of the posts I plan to write.