Creating Selective hapi Plugins

It is common to use the same hapi plugin across multiple applications. But, what if you need to expose a different set of routes to different users of your plugin? For example, assume you are creating a store locator plugin for an e-commerce site. You will probably need routes to create, retrieve, update, and delete store locations. Admin users should be able to perform all of these operations, but normal users should only be able to retrieve data.

One possible solution is to refactor your code into multiple plugins, where each plugin is tailored to its specific consumer. This is a perfectly valid solution. However, if a lot of functionality is shared between the split up plugins, then you are likely going to need yet another module to hold the shared code.

An alternative solution is to keep your logically grouped code together, and change the plugin's exposed interface depending on your consumer. Returning to our store locator example, our plugin would have two interfaces - a normal user interface, and an admin interface. The normal user interface would only allow for retrieving information, while the admin interface would provide write privileges. This can be accomplished using two separate servers, or a single server with two connections.

A Simple Example

Before getting started, you should download the source code for the demo. In this example, we're going to create a single server, with two distinct connections. The server will load two plugins - hapi-swagger for documentation, and a toy plugin named selector that exports a different set of routes depending on the consumer.

Our project's package.json contains two commands for running our server. Running npm start executes the file index.js, causing a normal user connection to be created on port 4000. An admin connection is also configured on port 5000. Then, the two plugins are registered, and the server is started. Once the server is running, each connection's routing table is printed to the console.

You can also start the application using rejoice, the hapi command line tool. npm run rejoice runs rejoice with the configuration stored in config.json. This will create the same server as running index.js directly, however no output will be printed to the console.

The Plugin

Our plugin code is stored in plugin.js. It creates two interfaces using the select() method. The results of select() are based off of the labels assigned to each connection.

Next, three routes are configured. The first route, GET /foo is added to the normal user connection. The configuration of this route is shown below. Note that we must check that the number of user connections is greater than zero before registering the route. This lets our plugin work with servers that don't define a user connection. It's also worth mentioning that the tags applied to the route are for use with hapi-swagger, and do not actually change our plugin's behavior.

var user = server.select('user');

if (user.connections.length > 0) {  
  user.route({
    method: 'GET',
    path: '/foo',
    config: {
      description: 'A route that foos',
      tags: ['api', 'user'],
      handler: function(request, reply) {
        return reply('foo');
      }
    }
  });
}

Next, a GET /bar route is added to the admin connection in the same fashion. Finally, a GET /baz route is added to the server, making it available to all connections.

Output

The output from running npm start is shown below. Notice that the routes associated with each label match our configuration. You can also verify the routes listed on the appropriate ports.

$ npm start

> hapi-api-selector@1.0.0 start /Users/cjihrig/Desktop/selector
> node index.js

Server started

Displaying routes:  
Label 'user':  
    GET /baz
    GET /foo

Label 'admin':  
    GET /bar
    GET /baz

The documentation automatically generated by hapi-swagger is also available at http://localhost:4000/documentation.

Note: At the time of writing, there is a bug in hapi-swagger when working with multiple connections. The bug causes only one of the connection's documentation sites to be displayed. You can work around the bug by creating two separate servers, instead of a single server with two connections.

Conclusion

This post has shown how you can avoid time consuming, sometimes unnecessary code refactors, by adding some additional configuration to your hapi server.