Xervo

Node.js and Hapi - Creating a REST API

Node.js and Hapi - Creating a REST API

This post has been updated to use the latest changes in Hapi Version 2.

A while back, we covered how to create a REST API using express, but express is not the only option for building a REST API in Node.js. Hapi is a configuration-centric framework for building web applications and APIs which makes building a REST API a snap.

Getting Started

Note that this tutorial will follow the Express tutorial closely to help illustrate the differences between the two frameworks.

There are some things to consider when choosing to use Hapi for your API:

  • Hapi requires a minimum of Node.js version 0.10.x
  • When creating a Hapi server, the port parameter must be of type Number - if you are using an environment variable, you must convert the variable to a Number
  • If you plan to deploy your Hapi application to a PaaS provider, you must listen on host 0.0.0.0 rather than localhost or 127.0.0.1
  • All of the code for this post is available on GitHub

Just like before, you'll need to initialize your application's package.json file either manually or using the npm init command. Once complete, create an index.js script then install the Hapi module and save it to your package.json.

npm install hapi --save

Create a Server

The first thing to do is require Hapi and create a server.

var Hapi = require('hapi');
var server = Hapi.createServer('0.0.0.0', parseInt(process.env.PORT, 10) || 3000);

This creates a Hapi server that you can use to route requests. Next is to add a route - adding routes in Hapi is a little different from adding routes in Express because Hapi routes allow a lot more configuration as you will see later.

server.route({
  method: 'GET'
, path: '/'
, handler: function(req, reply) {
    reply('i am a beautiful butterfly');
  }
});

Hapi route handlers only have a request parameter which you will use to send a response. Notice how no content type is specified in the response. Hapi will automatically set the content type based on the data that is sent. With an index route setup, all that's left to do is listen for incoming requests. Because the server is initialized with the host and port number, all you need to do is start the server up. Here is the complete server.

var Hapi = require('hapi');
var server = Hapi.createServer('0.0.0.0', parseInt(process.env.PORT, 10) || 3000);

server.route({
  method: 'GET'
, path: '/'
, handler: function(req, reply) {
    reply('i am a beautiful butterfly');
  }
});

server.start();

Start the application then curl http://localhost:3000 (or visit in your browser)

$ curl http://localhost:3000 -i

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 26
Cache-Control: no-cache
Connection: keep-alive

i am a beautiful butterfly

Notice that the content type header is set correctly without explicitly being set.

Basic REST API

Now that we have a very basic Hapi application we can dive into a more complex application. If you need more information on what a RESTful API is check out the wikipedia article.

REST APIs are made up of four method types, GET, PUT, POST, and DELETE and with Hapi we can easily do them all. For the rest of this article we're going to build a simple API that allows you to get and save quotes.

To get started we'll start with a basic Hapi application that just has an array of quotes. Each quote is an object with the author and text.

var Hapi = require('hapi');

var quotes = [
  {
    author: 'Audrey Hepburn'
  , text: 'Nothing is impossible, the word itself says \'I\'m possible\'!'
  }
, {
    author: 'Walt Disney'
  , text: 'You may not realize it when it happens, but a kick in the teeth may be the best thing in the world for you'
  }
, {
    author: 'Unknown'
  , text: 'Even the greatest was once a beginner. Don\'t be afraid to take that first step.'
  }
, {
    author: 'Neale Donald Walsch'
  , text: 'You are afraid to die, and you\'re afraid to live. What a way to exist.'
  }
];

var server = Hapi.createServer('0.0.0.0', parseInt(process.env.PORT, 10) || 3000);

Now, let's add a /quote route to return this array of quotes.

server.route({
  method: 'GET'
, path: '/quotes'
, handler: function(req, reply) {
    reply(quotes);
  }
});

Let's add the ability to retrieve a random quote. We'll add a new route /random; then we'll calculate a random index, grab the quote, and return it.

server.route({
  method: 'GET'
, path: '/random'
, handler: function(req, reply) {
    var id = Math.floor(Math.random() * quotes.length);
    reply(quotes[id]);
  }
});

The next thing we'll add is the ability to grab a single quote by ID. The way you do this is by adding a parameter to the route, so the route will look like /quote/id and the ID will be available using req.params.

server.route({
  method: 'GET'
, path: '/quote/{id?}'
, handler: function(req, reply) {
    if (req.params.id) {
      if (quotes.length <= req.params.id) {
        return reply('No quote found.').code(404);
      }
      return reply(quotes[req.params.id]);
    }
    reply(quotes);
  }
});

In the above the code you can see that we used the parameter {id?} which means that the ID parameter is optional. First, we check to make sure that the ID parameter was supplied then that the ID requested is in the range of quotes we have. If not then a 404 error is returned, meaning that the quote wasn't found.

Next up, we will add quotes to the array which will be done using a POST method. This is a good example of Hapi route configuration. In this POST route, we will add a validation object to ensure that all data is posted; otherwise, an error is returned.

server.route({
  method: 'POST'
, path: '/quote'
, config: {
    handler: function(req, reply) {
      var newQuote = {
        author: req.payload.author
      , text: req.payload.text
      };
      quotes.push(newQuote);
      reply(newQuote);
    }
  , validate: {
      payload: {
        author: Hapi.types.String().required()
      , text: Hapi.types.String().required()
      }
    }
  }
});

Notice that in Hapi, there was no need for extra middleware to parse the request body like there is with Express.

A valid POST with the new quote in the response

$ curl -X POST -H "Content-Type: application/json" -d '{ "author": "Matt Hernandez", "text": "Hapi is amazingly useful for building APIs." }' -i http://localhost:3000/quote
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 80
Cache-Control: no-cache
Connection: keep-alive

{"author":"Matt Hernandez","text":"Hapi is amazingly useful for building APIs."}

An Invalid POST (no text property) with an error response

$ curl -X POST -H "Content-Type: application/json" -d '{ "author": "Matt Hernandez" }' -i http://localhost:3000/quote
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 95
Cache-Control: no-cache
Connection: keep-alive

{"statusCode":400,"error":"Bad Request","message":"the value of text is not allowed to be undefined","validation":{"source":"payload","keys":["text"]}}

The last thing thing we'll add is the ability to delete a quote. This will be achieved using the DELETE method.

server.route({
  method: 'DELETE'
, path: '/quote/{id}'
, handler: function(req, reply) {
    if (quotes.length <= req.params.id) {
      return reply('No quote found.').code(404);
    }
    quotes.splice(req.params.id, 1);
    reply(true);
  }
});

The above function starts by checking the length of our quotes to make sure we don't try to delete something that isn't there. If no error we splice the array to remove the quote and return an empty response to the client.

DELETE the quote that was just added

$ curl -X DELETE http://localhost:3000/quote/4 -i
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 4
Cache-Control: no-cache
Connection: keep-alive

true

That covers the basics. This is only a drop in the bucket of what Hapi has and can do. Again, the complete code for this tutorial is available on GitHub, but it has been structured in a more maintainable way to give you an example of how to structure your Hapi APIs. Feel free to leave any questions below.

What is Xervo?

Xervo makes deploying applications in the public cloud or your own data center easy. Node.js, PHP, Java, Python, Nginx, and MongoDB supported. Full Docker support included in Enterprise version. It’s free to get started.

Share This Article

comments powered by Disqus