Getting Started with Hapi 17
hapi.js
18 January 2018Hapi 17 was recently released, and brings an exciting change — in an effort to drive adoption of async/await, the entire codebase replaced callbacks with the newer language feature.
This makes for some changes to how you configure your application. The intent of this post is to get you up and running as quickly as possible.
Installation
npm i --save hapi
or,
yarn add hapi
Setting up your server
const Hapi = require("hapi");
// define some constants to make life easier
const DEFAULT_HOST = "localhost";
const DEFAULT_PORT = 3000;
const RADIX = 10;
You can change the default host and port to your liking, this is just an example of some common defaults.
Major change — no more connections, you define 1 server which equals 1 connection. Only relevant if you used to have (for instance) an API server and a UI server defined in one place previously (using server.connection API - that’s gone now).
Also, Hapi.server is no longer a class-like structure so no more new keyword - just use the Hapi.server method directly:
// define your server
const server = Hapi.server({
host: process.env.HOST || DEFAULT_HOST,
port: parseInt(process.env.PORT, RADIX) || DEFAULT_PORT,
app: {}
});
We use the HOST
and PORT
values passed in via an environment variable (common method used in cloud server hosting) and fall
back to our previously defined default host/port if no such environment variable is set.
Note that Hapi expects port to be an integer, so use parseInt
to make sure the value passed from the environment is correct type.
Enclosing in an async function
The methods to register plugins and start your server are asynchronous, and you use the await keyword to wait for their return value. For it to be valid syntactically to use the await keyword, you must be within an async function — so we wrap everything in a main function, which you can name whatever you like:
const myServer = async () => {
};
myServer(); // don't forget to call it
We want to make sure to call the function, or nothing will happen — so we add the call up front so we don’t forget.
Error-handling
If Hapi can’t start, or can’t register a plugin, it throws an error. We should therefore be ready to handle such an error,
so we wrap in a try..catch
:
const myServer = async () => {
try {
} catch (err) {
console.log("Hapi error starting server", err);
}
};
myServer(); // don't forget to call it
In your catch block, at the very least you should log something to the console so you have an indication why it failed - otherwise it will just silently exit.
Registering plugins
Inside the try
block above, we register any plugins we may need.
This could be Hapi official plugins for things like Logging, Static File serving, caching, exception handling, etc.
But also, it’s a good idea to modularize your web application in to the plugin approach too (so, a plugin you define for everything to do with one piece of functionality you provide, another plugin for a separate piece of functionality, etc).
For now, let’s assume we’re going to be building an API that has 1 route, that returns “Hello World”, so we’re going to make that a plugin later in this post.
We will register it like so:
try { // this is the try block from above
await server.register(api);
} catch (err) { // ...etc
In server.register
, we can pass a single object (1 plugin) or an array of objects (multiple plugins).
And we’ll require it at the top of the file:
const Hapi = require("hapi");
const api = require("./api");
Starting your server
try { // this is the try block from above
await server.register(api);
await server.start();
console.log(`Hapi server running at ${server.info.uri}`);
} catch (err) { // ...etc
This starts the server after registering your plugin, then logs out to the console the URI it started up on.
Sharing properties within your app
The app property on the object you pass to Hapi.server is optional, but is a great place to store variables that you will use throughout your application.
Whatever is set on that object will be available on every instance of server being passed (think: Plugins, Extension Points) and every time you have a request object (think: Route Handlers, etc).
For example, if you have app: { foo: "bar" }
then in your route handler, given:
(request, reply) => {
console.log(request.server.settings.app.foo); // will log "bar"
}
and in a plugin definition:
async (server, options) => {
console.log(server.settings.app.foo); // logs "bar"
}
Defining a plugin
So to contain your functionality of an API route that replies “Hello, World” you create the file, with a path relative to your
file above of ./api/index.js
(so if your server is at YOUR_DIRECTORY/server.js
then this one will be YOUR_DIRECTORY/api/index.js
:
module.exports = {
name: "ApiPlugin",
register: async (server, options) => {
server.route([
{
method: "GET",
path: "/api/hello",
handler: async (request, h) => {
return "Hello, World";
}
}
]);
}
}
Note: both name
and register
are required. name should be a unique name for your plugin, and register contains your
registration method for the plugin.
In server.route
, we can again pass a single object (1 route) or an array of objects (multiple routes).
Method is the HTTP method that the route will respond to, while path defines the path that will be matched — see https://hapijs.com/api#path-parameters for path parameters.
In the handler method signature, request
is the request object and h
is a response toolkit — you won’t use this too often.
Whatever you return from the handler is what is sent to the client — Content-Type
is inferred from what you send.
If you return an object from a handler, for instance, it will be sent back as application/json
without you doing anything special.
If you return a string, it will be text/plain
.
To get more control over the response, you can use h.response
method, so let’s say you want to make it HTML - first, you create
a response:
const response = h.response('<html><head><title>hi</title></head><body>Hello</body></html>');
Then you can set the Content-Type:
response.type("text/html");
and then you must return it:
return response;
You can do redirects with
h.redirect(PATH_TO_REDIRECT)
You can set a cookie with
h.state(COOKIE_NAME, COOKIE_VALUE, <OPTIONS>)
Running your server
You can now start things up by running:
node server.js
To go further with your application, check out the official Hapi API docs.