Building APIs with Hapi.js
By Wyatt Preul

Introduction

hapi is a feature-rich web and services framework for Node.js. It helps developers focus on solving real business problems instead of having to worry about things like caching and validation. Additionally, hapi employs a plugin architecture, which makes it a perfect fit for cross-team development workflows where you can compose servers of varying features. Another benefit of this design choice is that servers can be pulled apart and decomposed into many different services more easily.

##

What we are building

In the following, we will build a simple hapi plugin that can be added to a hapi server. Some familiarity with Node.js and JavaScript is expected, as well as an understanding of web services.

The service we are making will have a single route that takes a string as input on the path and returns the pascal casing of the string. This is a very simple service; it is not entirely useful, but should help provide a good starting point to using hapi with plugins.

Step 1

Start by creating a folder and running npm init in it. How you answer the prompts is up to you. This will create a package.json file in the folder you just created.

The next step is to install lodash and add it to the package.json file. This will be used for the casing functionality. Do this by running npm install lodash --save.

Now create an index.js file and add the following contents to it:

// file: /index.js

'use strict'

const _ = require('lodash')

module.exports = function (input) {
 return _.capitalize(_.camelCase(input))
}

Step 2

Everything in the index.js file looks like it will work, but we won’t really know until we have tested it. To do this, we will use the test framework lab and assertion module code. Install both by running npm install code lab --save-dev. Next, create a folder named test and add an index.js file to it with the following contents:

/// file: /test/index.js
'use strict'

const Code = require('code')
const Lab = require('lab')
const Pascal = require('../')

// shortcuts
const lab = exports.lab = Lab.script()
const describe = lab.describe
const it = lab.it
const expect = Code.expect


describe('Pascal', () => {
 it('converts a string to pascal case on spaces', (done) => {
   var result = Pascal('something else')
   expect(result).to.equal('SomethingElse')
   done()
 })
})

lab doesn’t create any global variables and neither does code, which is part of the reason why you need to export a lab script property. To run the test above, you can either execute lab on the command line, or update the package.json scripts tag to look like the following:

"scripts": {
 "test": "lab"
}

Then run npm test. When you run the tests, the output will look like the following:

.

1 tests complete
Test duration: 8 ms
No global variable leaks detected

Step 3

The next step is to refactor our code into a hapi plugin. Expose the functionality through a decoration on the replyfunction, then test these changes. A hapi plugin must export a register function that accepts the following arguments: server, options, next. next is a function that you will need to execute when you are done setting up your plugin. The other part is to export an attributes property on the register function that includes your plugin name and version, or simply the contents of the package.json file. Go ahead and update the index.js in the root of the project with the following code (which replaces the existing code from step 1):

'use strict'

const _ = require('lodash')

exports.register = function (server, options, next) {
 server.decorate('reply', 'pascal', pascal)
 server.route({
   method: 'GET',
   path: '/{toPascal}',
   handler: function (request, reply) {
     reply.pascal(request.params.toPascal)
   }
 })

 next()
}

exports.register.attributes = {
 pkg: require('./package.json')
}

const pascal = function (input) {
 this.response(_.capitalize(_.camelCase(input)))
}

A lot is going on in the above code. Let’s walk through it all.

First, we change our pascal function into one that is bound with the reply context, allowing us to call this.response to set the response payload. The pascal function is bound by calling server.decorate, the documentation for which is found in the hapi API. After we have pascal set up on reply, we can add a route to the server and call reply.pascal to send a response with path parameter pascalized in the response body.

Step 4

At this point, we should verify that what we think is happening actually does happen. Install hapi as a devDependency by running npm install hapi --save-dev. Since this is a plugin, we don’t directly depend on hapi; instead, we expect it to be a peerDependency. However, for testing purposes, we will need hapi installed.

hapi has a built-in function for mocking requests, named inject. We will use inject to execute our route without needing to listen on a port and send real HTTP traffic to the server. As a result, our tests execute faster and are simpler to implement. Below is the updated test code, which registers our plugin with a hapi server then executes the route we added.

'use strict'

const Code = require('code')
const Hapi = require('hapi')
const Lab = require('lab')
const Pascal = require('../')

// shortcuts
const lab = exports.lab = Lab.script()
const describe = lab.describe
const it = lab.it
const expect = Code.expect


describe('Pascal', () => {
  it('converts a string to pascal case on spaces', (done) => {
    var server = new Hapi.Server()
    server.connection()
    server.register(Pascal, (err) => {
      expect(err).to.not.exist()
      server.inject('/something else', (res) => {
        expect(res.result).to.equal('SomethingElse')
        done()
      })
    })
  })
})

When you execute the test on the command line using npm test you will have the following output:

.

1 tests complete

Test duration: 39 ms

No global variable leaks detected

Bonus step

We should really add code coverage reporting of our plugin and expect it to be 100% covered. To do this, add a -c flag to the lab command in the package.json scripts area. The resulting execution of npm test should look like the following:

.

1 tests complete
Test duration: 38 ms
No global variable leaks detected
Coverage: 100.00%

Summary

hapi plugins are powerful, easy to create, composable bits of functionality. Hopefully, from the above example, you can start to see how adding various plugins to a hapi server becomes useful. This will become even more apparent if you are on a large team or teams and are working to build out a feature-rich server together. The ability to pull together features from across teams and have them work well together, with the added assurance that conflicts will be detected at start time, well before a customer interacts with your services, is absolutely necessary for any Node.js team.

Having reusable code that plays nicely with other code is also extremely valuable for any organization. Instead of wasting effort trying to integrate services and features, hapi has undertaken most of the effort for you. Additionally, since plugins are simply Node.js modules, they can be published to a npm repository.

The code for this tutorial is located at https://github.com/geek/pascal-plugin and https://www.npmjs.com/package/pascal-plugin.

 

Subscribe to our monthly newsletter!
join the discussion