Plugins
Having every route and server configuration in one script can get unwieldy pretty quickly. Fortunately, Hapi provides a plugin interface that can be used to split our project up into logical components, or to quickly hook new functionality into our Hapi server from npm.
Before diving into plugin architecture, we need to understand that Hapi provides an interface for grouping servers together, called a “pack”. Every Hapi.Server belongs to a Hapi.Pack, regardless of whether or not it was explicitly added to one or not. We’ll explain this concept later on; for now, you only need to know that the plugin interface is built atop the pack interface.
For example, we can easily activate the “lout” plugin — developed by Spumko — to provide a “/docs” route, displaying the server’s routing table as a set of endpoints. Use npm to install lout, and replace the server.start() call with the following.
var lout = require("lout");server.pack.register({plugin: lout}, function(err) {
if (err) throw err;
server.start(function() {
console.log("Hapi server started @ " + server.info.uri);
});
});
Navigate to http://localhost:8080/docs and we’ll receive a list of routes we have configured as well as the HTTP method they use. This functionality is added by lout — if you want to see configurable options, visit the GitHub repository.
Creating a Plugin
Plugins are regular modules that export a register function, called when the plugin is registered to a pack. Plugins can live in node_modules or any other directory resolvable by Node’s module system. npm modules can be required using just the name of the plugin (which we did with lout above), other modules need to be specified relative to the working directory of the server.
Create a “plugins” directory, and create a sub-directory named “example”. In this directory, run “npm init” as we did when we started the MyApp project.
bash-3.2$ mkdir -p plugins/example
bash-3.2$ cd plugins/example/
bash-3.2$ npm init
name: (example)
version: (0.0.0) 0.0.1
description: Example Hapi plugin
entry point: (index.js)
test command:
git repository:
keywords:
author: Fionn Kelleher <me@fionn.co>
license: (ISC) BSD
About to write to /Users/fionnkelleher/MyApp/plugins/example/package.json:
{
"name": "example",
"version": "0.0.1",
"description": "Example Hapi plugin",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"author": "Fionn Kelleher <me@fionn.co>",
"license": "BSD"
}
Is this ok? (yes)
In the directory, create index.js with the following contents.
exports.register = function(plugin, options, next) {
next();
};exports.register.attributes = {
pkg: require("./package.json")
}
We’re exporting a register function that will be called as soon as the plugin has been required. It has the signature function(plugin, options, next) {…}.
The register function is the most important part, as it provides a means of configuring the plugin. To explain the arguments the function takes:
- plugin — the plugin interface that we utilise to define routes, methods and more that are associated with the plugin.
- options — an object containing any options passed when requiring the plugin.
- next — a function that is required to be called when the plugin is considered to be in a usable state.
Let’s expand our barebones plugin to define a route.
Our plugin will now resemble the following:
exports.register = function(plugin, options, next) {
plugin.route({
path: "/my/plugin",
method: "GET",
handler: function(request, reply) {
reply("This is a reply from a route defined in my plugin!");
}
});
next();
};exports.register.attributes = {
pkg: require("./package.json")
};
Modify MyApp’s index.js to require the plugin and register it.
server.pack.register([
{ plugin: require("lout") },
{ plugin: require("./plugins/example") }
], function(err) {
if (err) throw err;
server.start(function() {
console.log("Hapi server started @ " + server.info.uri);
});
});
Restart the server and send a request to http://localhost:8080/my/plugin and we’ll be greeted with “This is a reply from a route defined in my plugin!”.
Passing Configuration To Plugins
It’s possible to pass a configuration object to your plugins, for example if we were wishing to make the route’s path configurable, we could modify our plugin to the following:
var Hoek = require("hoek");
var defaults = {
route: "/my/plugin"
};
exports.register = function(plugin, options, next) {
options = Hoek.applyToDefaults(defaults, options);plugin.route({
path: options.route,
method: "GET",
handler: function(request, reply) {
reply("This is a reply from a route defined in my plugin!");
}
});
next();
};
exports.register.attributes {
pkg: require("./package.json")
};
Make sure you have the Hoek module installed as a dependency. Hoek is a utility library developed by Spumko, and includes an easy method to apply user configured options to an object of default values.
When we modify index.js to the following, the route will be changed to “/my/custom/route”.
server.pack.register([
{ plugin: require("lout") },
{
plugin: require("./plugins/example"),
options: { route: "/my/custom/route" }
}
], function(err) {
if (err) throw err;
server.start(function() {
console.log("Hapi server started @ " + server.info.uri);
});
});