Hapi.js

Overview

As they say on their website

Concepts

Before reading

The following slides include Hapi mechanics you should consider first before reading code samples.

For progressive understanding of the concepts and samples, please prefer reading this up to date documentation instead of the official site guides.

We'll skip on purpose the views and rendering part since we only care about building bridges between endpoints and services.

Hapi ecosystem

  • Hapi has created its own ecosystem
  • Hapi well known contributions are most of the time cleverly written
  • If you reach an issue, first think it has been addressed by the community

High level mechanic

  • A hapi server is object with an API letting you configure/declare
    • auth
    • cache
    • methods (own cache)
    • routes (own auth, cache + prerequisites, validation)
  • A hapi server registers plugins to
    • decorate server, request or reply
    • listen to events
    • hook lifecycles
  • A hapi server is bound to connections for listening
  • Routes are set per { server, connection(s) } binding

Mid level mechanic

  • Requests and responses have an exposed lifecycle
  • The server is an EventEmitter (podium) (no behavior control)
  • The server exposes an extension API (behavior control)
  • Server constructor accepts default config for behaviors
  • Server API overrides config and activates behaviors

Playing with the lifecycle

Playing with the configuration

Low level mechanic

  • Hapi idiomatic decoration via plugins is to be prefered against manual injection, which includes
    • server context
    • request context
    • per request context
    • reply behaviors
  • Same for events listening and lifecycle hooking

Reply API is confusing at first. The reply function understands values, buffers, streams, promises.

Decoration

Reply

Basic usage

Server

const Hapi = require('hapi');
const server = new Hapi.Server();
const Hapi = require('hapi');
const CatboxRedis = require('catbox-redis');

const server = new Hapi.Server({
  cache: CatboxRedis,
  connections: {
    load: {
      maxHeapUsedBytes: 2e8, // bytes
      maxEventLoopDelay: 20 // ms
    }
  },
  useDomains: false
});

with some options

The simplest instantiation

Connections

const abcServer = server.connection({
  port: 3000,
  labels: ['a', 'b', 'c']
});
const abcServer = server.select(['a']);

Retrievable by label

Auth

const HapiAuthBasicPlugin = require('hapi-auth-basic');

server.register(HapiAuthBasicPlugin); // register 'basic' scheme

server.auth.strategy('simple', 'basic', {
  validateFunc: (request, username, password, callback) => {
    callback(null, true, someUser);
  }
});

server.route({ method: 'GET', path: '/', config: { auth: 'simple' } });

Basic

server.auth.scheme('custom', (server, options) => ({
  authenticate: (request, reply) => {
    reply.continue({ credentials: someUser });
  }
}));

server.auth.strategy('default', 'custom');

Custom

State management

State management

  • It's all about cookies
  • It can use configured cache for persistence

Routes

Route configuration

server.route({
  method: 'GET',
  path: '/user/{userId}',
  config: {
    auth: 'default',
    cache: { expiresIn: 5000 },
    ext: {
      onPreHandler: (request, reply) => { reply.continue(); }
    },
    validate: {
      params: {
        userId: Joi.string().required()
      }
    },
    handler: (request, reply) => reply(fetchUser(request.params.userId)
  }
});

Warning: no nested route system included

Validation

Validation with Joi

const schema = Joi.object().keys({
    username: Joi.string().alphanum().min(3).max(30).required(),
    password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
    access_token: [Joi.string(), Joi.number()],
    birthyear: Joi.number().integer().min(1900).max(2013),
    email: Joi.string().email()
}).with('username', 'birthyear').without('password', 'access_token');

Hapi has several places you can provide a Joi schema (at least an object with a validate function)

Here is a sample

But the best is to visit their documentation

Tests

Request injection

The simplest case

server.route({
  method: 'GET',
  path: '/',
  handler: (request, reply) => reply(42)
});

server.inject('/').should.become(42);

Exercises

Exercises

start a simple server with a single route

request it with a postman (or what you prefer)

instrument it to get the time spent in the handlers

Of course, keep helping yourself with the documentation: https://github.com/hapijs/hapi/blob/master/API.md

?

Hapi

By Alexis Tondelier

Hapi

Hapi.js main concepts

  • 871