16.6.x API Reference

Server

The Server object is the main application container. The server manages all incoming connections along with all the facilities provided by the framework. A server can contain more than one connection (e.g. listen to port 80 and 8080).

new Server([options])

Creates a new Server object where:

Note that the options object is deeply cloned and cannot contain any values that are unsafe to perform deep copy on.

const Hapi = require('hapi');
const server = new Hapi.Server({
    cache: require('catbox-redis'),
    load: {
        sampleInterval: 1000
    }
});

Server properties

server.app

Provides a safe place to store server-specific run-time application data without potential conflicts with the framework internals. The data can be accessed whenever the server is accessible. Initialized with an empty object.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.app.key = 'value';

const handler = function (request, reply) {

    return reply(request.server.app.key);
};

server.connections

An array containing the server's connections. When the server object is returned from server.select(), the connections array only includes the connections matching the selection criteria.

const server = new Hapi.Server();
server.connection({ port: 80, labels: 'a' });
server.connection({ port: 8080, labels: 'b' });

// server.connections.length === 2

const a = server.select('a');

// a.connections.length === 1

Each connection object contains:

server.decorations

Provides access to the decorations already applied to various framework interfaces. The object must not be modified directly, but only through server.decorate. Contains the following keys: - 'request' - decorations on the Request object. - 'reply' - decorations on the reply interface. - 'server' - decorations on the Server object.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const success = function () {

    return this.response({ status: 'ok' });
};

server.decorate('reply', 'success', success);
return server.decorations.reply;		// ['success']

server.info

When the server contains exactly one connection, info is an object containing information about the sole connection where:

When the server contains more than one connection, each server.connections array member provides its own connection.info.

const server = new Hapi.Server();
server.connection({ port: 80 });

// server.info.port === 80

server.connection({ port: 8080 });

// server.info === null
// server.connections[1].info.port === 8080

server.load

An object containing the process load metrics (when load.sampleInterval is enabled):

const Hapi = require('hapi');
const server = new Hapi.Server({ load: { sampleInterval: 1000 } });

console.log(server.load.rss);

server.listener

When the server contains exactly one connection, listener is the node HTTP server object of the sole connection.

When the server contains more than one connection, each server.connections array member provides its own connection.listener.

const Hapi = require('hapi');
const SocketIO = require('socket.io');

const server = new Hapi.Server();
server.connection({ port: 80 });

const io = SocketIO.listen(server.listener);
io.sockets.on('connection', (socket) => {

    socket.emit({ msg: 'welcome' });
});

server.methods

An object providing access to the server methods where each server method name is an object property.

const Hapi = require('hapi');
const server = new Hapi.Server();

const add = function (a, b, next) {

    return next(null, a + b);
};

server.method('add', add);

server.methods.add(1, 2, (err, result) => {

    // result === 3
});

server.mime

Provides access to the server MIME database used for setting content-type information. The object must not be modified directly but only through the mime server setting.

const Hapi = require('hapi');

const options = {
    mime: {
        override: {
            'node/module': {
                source: 'steve',
                compressible: false,
                extensions: ['node', 'module', 'npm'],
                type: 'node/module'
            }
        }
    }
};

const server = new Hapi.Server(options);
// server.mime.path('code.js').type === 'application/javascript'
// server.mime.path('file.npm').type === 'node/module'

server.plugins

An object containing the values exposed by each plugin registered where each key is a plugin name and the values are the exposed properties by each plugin using server.expose(). Plugins may set the value of the server.plugins[name] object directly or via the server.expose() method.

exports.register = function (server, options, next) {

    server.expose('key', 'value');
    // server.plugins.example.key === 'value'
    return next();
};

exports.register.attributes = {
    name: 'example'
};

server.realm

The realm object contains server-wide or plugin-specific state that can be shared across various methods. For example, when calling server.bind(), the active realm settings.bind property is set which is then used by routes and extensions added at the same level (server root or plugin). Realms are a limited version of a sandbox where plugins can maintain state used by the framework when adding routes, extensions, and other properties.

The server.realm object should be considered read-only and must not be changed directly except for the plugins property which can be directly manipulated by each plugin, setting its properties inside plugins[name].

exports.register = function (server, options, next) {

    console.log(server.realm.modifiers.route.prefix);
    return next();
};

server.registrations

When the server contains exactly one connection, registrations is an object where each key is a registered plugin name and value contains:

When the server contains more than one connection, each server.connections array member provides its own connection.registrations.

server.root

The root server object containing all the connections and the root server methods (e.g. start(), stop(), connection()).

server.settings

The server configuration object after defaults applied.

const Hapi = require('hapi');
const server = new Hapi.Server({
    app: {
        key: 'value'
    }
});

// server.settings.app === { key: 'value' }

server.version

The hapi module version number.

const Hapi = require('hapi');
const server = new Hapi.Server();
// server.version === '8.0.0'

server.auth.api

An object where each key is a strategy name and the value is the exposed strategy API. Available on when the authentication scheme exposes an API by returning an api key in the object returned from its implementation function.

When the server contains more than one connection, each server.connections array member provides its own connection.auth.api object.

const server = new Hapi.Server();
server.connection({ port: 80 });

const scheme = function (server, options) {

    return {
        api: {
            settings: {
                x: 5
            }
        },
        authenticate: function (request, reply) {

            const req = request.raw.req;
            const authorization = req.headers.authorization;
            if (!authorization) {
                return reply(Boom.unauthorized(null, 'Custom'));
            }

            return reply.continue({ credentials: { user: 'john' } });
        }
    };
};

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

console.log(server.auth.api.default.settings.x);    // 5

server.auth.default(options)

Sets a default strategy which is applied to every route where:

The default does not apply when the route config specifies auth as false, or has an authentication strategy configured (contains the strategy or strategies authentication settings). Otherwise, the route authentication config is applied to the defaults.

Note that if the route has authentication config, the default only applies at the time of adding the route, not at runtime. This means that calling default() after adding a route with some authentication config will have no impact on the routes added prior. However, the default will apply to routes added before default() is called if those routes lack any authentication config.

The default auth strategy configuration can be accessed via connection.auth.settings.default. To obtain the active authentication configuration of a route, use connection.auth.lookup(request.route).

const server = new Hapi.Server();
server.connection({ port: 80 });

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

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, reply) {

        return reply(request.auth.credentials.user);
    }
});

server.auth.scheme(name, scheme)

Registers an authentication scheme where:

The scheme method must return an object with the following keys:

When the scheme authenticate() method implementation calls reply() with an error condition, the specifics of the error affect whether additional authentication strategies will be attempted (if configured for the route). If the err passed to the reply() method includes a message, no additional strategies will be attempted. If the err does not include a message but does include the scheme name (e.g. Boom.unauthorized(null, 'Custom')), additional strategies will be attempted in the order of preference (defined in the route configuration). If authentication fails the scheme names will be present in the 'WWW-Authenticate' header.

When the scheme payload() method returns an error with a message, it means payload validation failed due to bad payload. If the error has no message but includes a scheme name (e.g. Boom.unauthorized(null, 'Custom')), authentication may still be successful if the route auth.payload configuration is set to 'optional'.

const server = new Hapi.Server();
server.connection({ port: 80 });

const scheme = function (server, options) {

    return {
        authenticate: function (request, reply) {

            const req = request.raw.req;
            const authorization = req.headers.authorization;
            if (!authorization) {
                return reply(Boom.unauthorized(null, 'Custom'));
            }

            return reply.continue({ credentials: { user: 'john' } });
        }
    };
};

server.auth.scheme('custom', scheme);

server.auth.strategy(name, scheme, [mode], [options])

Registers an authentication strategy where:

const server = new Hapi.Server();
server.connection({ port: 80 });

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

server.route({
    method: 'GET',
    path: '/',
    config: {
        auth: 'default',
        handler: function (request, reply) {

            return reply(request.auth.credentials.user);
        }
    }
});

server.auth.test(strategy, request, next)

Tests a request against an authentication strategy where:

Note that the test() method does not take into account the route authentication configuration. It also does not perform payload authentication. It is limited to the basic strategy authentication execution. It does not include verifying scope, entity, or other route properties.

const server = new Hapi.Server();
server.connection({ port: 80 });

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

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, reply) {

        request.server.auth.test('default', request, (err, credentials) => {

            if (err) {
                return reply({ status: false });
            }

            return reply({ status: true, user: credentials.name });
        });
    }
});

server.bind(context)

Sets a global context used as the default bind object when adding a route or an extension where:

When setting context inside a plugin, the context is applied only to methods set up by the plugin. Note that the context applies only to routes and extensions added after it has been set. Ignored if the method being bound is an arrow function.

const handler = function (request, reply) {

    return reply(this.message);
};

exports.register = function (server, options, next) {

    const bind = {
        message: 'hello'
    };

    server.bind(bind);
    server.route({ method: 'GET', path: '/', handler: handler });
    return next();
};

server.cache(options)

Provisions a cache segment within the server cache facility where:

const server = new Hapi.Server();
server.connection({ port: 80 });

const cache = server.cache({ segment: 'countries', expiresIn: 60 * 60 * 1000 });
cache.set('norway', { capital: 'oslo' }, null, (err) => {

    cache.get('norway', (err, value, cached, log) => {

        // value === { capital: 'oslo' };
    });
});

server.cache.provision(options, [callback])

Provisions a server cache as described in server.cache where:

If no callback is provided, a Promise object is returned.

Note that if the server has been initialized or started, the cache will be automatically started to match the state of any other provisioned server cache.

const server = new Hapi.Server();
server.connection({ port: 80 });

server.initialize((err) => {

    server.cache.provision({ engine: require('catbox-memory'), name: 'countries' }, (err) => {

        const cache = server.cache({ cache: 'countries', expiresIn: 60 * 60 * 1000 });
        cache.set('norway', { capital: 'oslo' }, null, (err) => {

            cache.get('norway', (err, value, cached, log) => {

                // value === { capital: 'oslo' };
            });
        });
    });
});

server.connection([options])

Adds an incoming server connection where:

Returns a server object with the new connections selected.

Must be called before any other server method that modifies connections is called for it to apply to the new connection (e.g. server.state()).

Note that the options object is deeply cloned (with the exception of listener which is shallowly copied) and cannot contain any values that are unsafe to perform deep copy on.

const Hapi = require('hapi');
const server = new Hapi.Server();

const web = server.connection({ port: 8000, host: 'example.com', labels: ['web'] });
const admin = server.connection({ port: 8001, host: 'example.com', labels: ['admin'] });

// server.connections.length === 2
// web.connections.length === 1
// admin.connections.length === 1

Special care must be taken when adding connections inside a plugin register() method. Because plugin connections selection happens before registration, any connection added inside the plugin will not be included in the server.connections array. For this reason, the server object provided to the register() method does not support the connection() method.

However, connectionless plugins (plugins with attributes.connections set to false) provide a powerful bridge and allow plugins to add connections. This is done by using the register() server argument only for adding the new connection using server.connection() and then using the return value from the connection() method (which is another server with the new connection selected) to perform any other actions that should include the new connection (only).

While this pattern can be accomplished without setting the plugin to connectionless mode, it makes the code safer and easier to maintain because it will prevent trying to use the server argument to manage the new connection and will throw an exception (instead of just failing silently). Without setting the plugin to connectionless mode, you must use server.root.connection() which will return a server object scoped for the root realm, not the current plugin.

For example:

exports.register = function (srv, options, next) {

    // Use the 'srv' argument to add a new connection

    const server = srv.connection();

    // Use the 'server' return value to manage the new connection

    server.route({
        path: '/',
        method: 'GET',
        handler: function (request, reply) {

            return reply('hello');
        }
    });

    return next();
};

exports.register.attributes = {
    name: 'example',
    connections: false
};

server.decoder(encoding, decoder)

Registers a custom content decoding compressor to extend the built-in support for 'gzip' and 'deflate' where:

const Zlib = require('zlib');
const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80, routes: { payload: { compression: { special: { chunkSize: 16 * 1024 } } } } });

server.decoder('special', (options) => Zlib.createGunzip(options));

server.decorate(type, property, method, [options])

Extends various framework interfaces with custom methods where:

Note that decorations apply to the entire server and all its connections regardless of current selection.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const success = function () {

    return this.response({ status: 'ok' });
};

server.decorate('reply', 'success', success);

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, reply) {

        return reply.success();
    }
});

server.dependency(dependencies, [after])

Used within a plugin to declare a required dependency on other plugins where:

The after method is identical to setting a server extension point on 'onPreStart'. Connectionless plugins (those with attributes.connections set to false) can only depend on other connectionless plugins (server initialization will fail even of the dependency is loaded but is not connectionless).

const after = function (server, next) {

    // Additional plugin registration logic
    return next();
};

exports.register = function (server, options, next) {

    server.dependency('yar', after);
    return next();
};

Dependencies can also be set via the register attributes property (does not support setting after):

exports.register = function (server, options, next) {

    return next();
};

register.attributes = {
    name: 'test',
    version: '1.0.0',
    dependencies: 'yar'
};

server.emit(criteria, data, [callback])

Emits a custom application event update to all the subscribed listeners where:

Note that events must be registered before they can be emitted or subscribed to by calling server.event(events). This is done to detect event name misspelling and invalid event activities.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.event('test');
server.on('test', (update) => console.log(update));
server.emit('test', 'hello');

server.encoder(encoding, encoder)

Registers a custom content encoding compressor to extend the built-in support for 'gzip' and 'deflate' where:

const Zlib = require('zlib');
const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80, routes: { compression: { special: { chunkSize: 16 * 1024 } } } });

server.encoder('special', (options) => Zlib.createGzip(options));

server.event(events)

Register custom application events where:

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.event('test');
server.on('test', (update) => console.log(update));
server.emit('test', 'hello');

server.expose(key, value)

Used within a plugin to expose a property via server.plugins[name] where:

exports.register = function (server, options, next) {

    server.expose('util', function () { console.log('something'); });
    return next();
};

server.expose(obj)

Merges an object into to the existing content of server.plugins[name] where:

exports.register = function (server, options, next) {

    server.expose({ util: function () { console.log('something'); } });
    return next();
};

Note that all properties of obj are deeply cloned into server.plugins[name], so you should avoid using this method for exposing large objects that may be expensive to clone or singleton objects such as database client objects. Instead favor the server.expose(key, value) form, which only copies a reference to value.

server.ext(events)

Registers an extension function in one of the available extension points where:

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.ext({
    type: 'onRequest',
    method: function (request, reply) {

        // Change all requests to '/test'
        request.setUrl('/test');
        return reply.continue();
    }
});

const handler = function (request, reply) {

    return reply({ status: 'ok' });
};

server.route({ method: 'GET', path: '/test', handler: handler });
server.start((err) => { });

// All requests will get routed to '/test'

server.ext(event, method, [options])

Registers a single extension event using the same properties as used in server.ext(events), but passed as arguments.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.ext('onRequest', function (request, reply) {

    // Change all requests to '/test'
    request.setUrl('/test');
    return reply.continue();
});

const handler = function (request, reply) {

    return reply({ status: 'ok' });
};

server.route({ method: 'GET', path: '/test', handler: handler });
server.start((err) => { });

// All requests will get routed to '/test'

server.handler(name, method)

Registers a new handler type to be used in routes where:

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ host: 'localhost', port: 8000 });

// Defines new handler for routes on this server
const handler = function (route, options) {

    return function (request, reply) {

        return reply('new handler: ' + options.msg);
    }
};

server.handler('test', handler);

server.route({
    method: 'GET',
    path: '/',
    handler: { test: { msg: 'test' } }
});

server.start(function (err) { });

The method function can have a defaults object or function property. If the property is set to an object, that object is used as the default route config for routes using this handler. If the property is set to a function, the function uses the signature function(method) and returns the route default configuration.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ host: 'localhost', port: 8000 });

const handler = function (route, options) {

    return function (request, reply) {

        return reply('new handler: ' + options.msg);
    }
};

// Change the default payload processing for this handler
handler.defaults = {
    payload: {
        output: 'stream',
        parse: false
    }
};

server.handler('test', handler);

server.initialize([callback])

Initializes the server (starts the caches, finalizes plugin registration) but does not start listening on the connection ports, where:

If no callback is provided, a Promise object is returned.

Note that if the method fails and the callback includes an error, the server is considered to be in an undefined state and should be shut down. In most cases it would be impossible to fully recover as the various plugins, caches, and other event listeners will get confused by repeated attempts to start the server or make assumptions about the healthy state of the environment. It is recommended to assert that no error has been returned after calling initialize() to abort the process when the server fails to start properly. If you must try to resume after an error, call server.stop() first to reset the server state.

const Hapi = require('hapi');
const Hoek = require('hoek');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.initialize((err) => {

    Hoek.assert(!err, err);
});

server.inject(options, [callback])

When the server contains exactly one connection, injects a request into the sole connection simulating an incoming HTTP request without making an actual socket connection. Injection is useful for testing purposes as well as for invoking routing logic internally without the overhead or limitations of the network stack. Utilizes the shot module for performing injections, with some additional options and response properties:

If no callback is provided, a Promise object is returned. The promise will only ever be resolved and never rejected. Use the statusCode to determine if the request was successful.

When the server contains more than one connection, each server.connections array member provides its own connection.inject().

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const handler = function (request, reply) {

    return reply('Success!');
};

server.route({ method: 'GET', path: '/', handler: handler });

server.inject('/', (res) => {

    console.log(res.result);
});

server.log(tags, [data, [timestamp]])

Logs server events that cannot be associated with a specific request. When called the server emits a 'log' event which can be used by other listeners or plugins to record the information or output to the console. The arguments are:

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.on('log', (event, tags) => {

    if (tags.error) {
        console.log(event);
    }
});

server.log(['test', 'error'], 'Test event');

server.lookup(id)

When the server contains exactly one connection, looks up a route configuration where:

returns the route public interface object if found, otherwise null.

const server = new Hapi.Server();
server.connection();
server.route({
    method: 'GET',
    path: '/',
    config: {
        handler: function (request, reply) {

            return reply();
        },
        id: 'root'
    }
});

const route = server.lookup('root');

When the server contains more than one connection, each server.connections array member provides its own connection.lookup() method.

server.match(method, path, [host])

When the server contains exactly one connection, looks up a route configuration where:

returns the route public interface object if found, otherwise null.

const server = new Hapi.Server();
server.connection();
server.route({
    method: 'GET',
    path: '/',
    config: {
        handler: function (request, reply) {

            return reply();
        },
        id: 'root'
    }
});

const route = server.match('get', '/');

When the server contains more than one connection, each server.connections array member provides its own connection.match() method.

server.method(name, method, [options])

Registers a server method. Server methods are functions registered with the server and used throughout the application as a common utility. Their advantage is in the ability to configure them to use the built-in cache and share across multiple request handlers without having to create a common module.

Methods are registered via server.method(name, method, [options]) where:

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

// Simple arguments

const add = function (a, b, next) {

    return next(null, a + b);
};

server.method('sum', add, { cache: { expiresIn: 2000, generateTimeout: 100 } });

server.methods.sum(4, 5, (err, result) => {

    console.log(result);
});

// Object argument

const addArray = function (array, next) {

    let sum = 0;
    array.forEach((item) => {

        sum += item;
    });

    return next(null, sum);
};

server.method('sumObj', addArray, {
    cache: { expiresIn: 2000, generateTimeout: 100 },
    generateKey: function (array) {

        return array.join(',');
    }
});

server.methods.sumObj([5, 6], (err, result) => {

    console.log(result);
});

// Synchronous method with cache

const addSync = function (a, b) {

    return a + b;
};

server.method('sumSync', addSync, { cache: { expiresIn: 2000, generateTimeout: 100 }, callback: false });

server.methods.sumSync(4, 5, (err, result) => {

    console.log(result);
});

server.method(methods)

Registers a server method function as described in server.method() using a configuration object where:

const add = function (a, b, next) {

    next(null, a + b);
};

server.method({
    name: 'sum',
    method: add,
    options: {
        cache: {
            expiresIn: 2000,
            generateTimeout: 100
        }
    }
});

server.on(criteria, listener)

Subscribe a handler to an event where:

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.event('test');
server.on('test', (update) => console.log(update));
server.emit('test', 'hello');

server.once(criteria, listener)

Same as calling server.on() with the count option set to 1.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.event('test');
server.once('test', (update) => console.log(update));
server.emit('test', 'hello');
server.emit('test', 'hello');       // Ignored

server.path(relativeTo)

Sets the path prefix used to locate static resources (files and view templates) when relative paths are used where:

Note that setting a path within a plugin only applies to resources accessed by plugin methods. If no path is set, the connection files.relativeTo configuration is used. The path only applies to routes added after it has been set.

exports.register = function (server, options, next) {

    // Assuming the Inert plugin was registered previously

    server.path(__dirname + '../static');
    server.route({ path: '/file', method: 'GET', handler: { file: './test.html' } });
    next();
};

server.register(plugins, [options], [callback])

Registers a plugin where:

If no callback is provided, a Promise object is returned.

Note that plugin registration are recorded on each of the available connections. When plugins express a dependency on other plugins, both have to be loaded into the same connections for the dependency requirement to be fulfilled. It is recommended that plugin registration happen after all the server connections are created via server.connection().

server.register({
    register: require('plugin_name'),
    options: {
        message: 'hello'
    }
 }, (err) => {

     if (err) {
         console.log('Failed loading plugin');
     }
 });

server.route(options)

Adds a connection route where:

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.route({ method: 'GET', path: '/', handler: function (request, reply) { return reply('ok'); } });
server.route([
    { method: 'GET', path: '/1', handler: function (request, reply) { return reply('ok'); } },
    { method: 'GET', path: '/2', handler: function (request, reply) { return reply('ok'); } }
]);

server.select(labels)

Selects a subset of the server's connections where:

Returns a server object with connections set to the requested subset. Selecting again on a selection operates as a logic AND statement between the individual selections.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80, labels: ['a', 'b'] });
server.connection({ port: 8080, labels: ['a', 'c'] });
server.connection({ port: 8081, labels: ['b', 'c'] });

const a = server.select('a');     // 80, 8080
const ac = a.select('c');         // 8080

server.start([callback])

Starts the server connections by listening for incoming requests on the configured port of each listener (unless the connection was configured with autoListen set to false), where:

If no callback is provided, a Promise object is returned.

Note that if the method fails and the callback includes an error, the server is considered to be in an undefined state and should be shut down. In most cases it would be impossible to fully recover as the various plugins, caches, and other event listeners will get confused by repeated attempts to start the server or make assumptions about the healthy state of the environment. It is recommended to assert that no error has been returned after calling start() to abort the process when the server fails to start properly. If you must try to resume after a start error, call server.stop() first to reset the server state.

If a started server is started again, the second call to start() will only start new connections added after the initial start() was called. No events will be emitted and no extension points invoked.

const Hapi = require('hapi');
const Hoek = require('hoek');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.start((err) => {

    Hoek.assert(!err, err);
    console.log('Server started at: ' + server.info.uri);
});

server.state(name, [options])

HTTP state management uses client cookies to persist a state across multiple requests. Registers a cookie definitions where:

State defaults can be modified via the server connections.routes.state configuration option.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

// Set cookie definition

server.state('session', {
    ttl: 24 * 60 * 60 * 1000,     // One day
    isSecure: true,
    path: '/',
    encoding: 'base64json'
});

// Set state in route handler

const handler = function (request, reply) {

    let session = request.state.session;
    if (!session) {
        session = { user: 'joe' };
    }

    session.last = Date.now();

    return reply('Success').state('session', session);
};

Registered cookies are automatically parsed when received. Parsing rules depends on the route state.parse configuration. If an incoming registered cookie fails parsing, it is not included in request.state, regardless of the state.failAction setting. When state.failAction is set to 'log' and an invalid cookie value is received, the server will emit a 'request-internal' event. To capture these errors subscribe to the 'request-internal' events and filter on 'error' and 'state' tags:

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.on('request-internal', (request, event, tags) => {

    if (tags.error && tags.state) {
        console.error(event);
    }
});

server.stop([options], [callback])

Stops the server's connections by refusing to accept any new connections or requests (existing connections will continue until closed or timeout), where:

If no callback is provided, a Promise object is returned.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.stop({ timeout: 60 * 1000 }, (err) => {

    console.log('Server stopped');
});

server.table([host])

Returns a copy of the routing table where:

The return value is an array where each item is an object containing:

Note that if the server has not been started and multiple connections use port 0, the table items will override each other and will produce an incomplete result.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80, host: 'example.com' });
server.route({ method: 'GET', path: '/example', handler: function (request, reply) { return reply(); } });

const table = server.table();

When calling connection.table() directly on each connection, the return value is the same as the array table item value of an individual connection:

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80, host: 'example.com' });
server.route({ method: 'GET', path: '/example', handler: function (request, reply) { return reply(); } });

const table = server.connections[0].table();

/*
    [
        {
            method: 'get',
            path: '/example',
            settings: { ... }
        }
    ]
*/

Server events

The server object inherits from Events.EventEmitter and emits the following events:

Note that the server object should not be used to emit application events as its internal implementation is designed to fan events out to the various plugin selections and not for application events.

When provided (as listed below) the event object includes:

The 'log' event includes the event object and a tags object (where each tag is a key with the value true):

server.on('log', (event, tags) => {

    if (tags.error) {
        console.log('Server error: ' + (event.data || 'unspecified'));
    }
});

The 'request' and 'request-internal' events include the request object, the event object, and a tags object (where each tag is a key with the value true):

server.on('request', (request, event, tags) => {

    if (tags.received) {
        console.log('New request: ' + request.id);
    }
});

The 'request-error' event includes the request object and the causing error err object:

server.on('request-error', (request, err) => {

    console.log('Error response (500) sent for request: ' + request.id + ' because: ' + err.message);
});

The 'response' and 'tail' events include the request object:

server.on('response', (request) => {

    console.log('Response sent for request: ' + request.id);
});

The 'route' event includes the route public interface, the connection, and the server object used to add the route (e.g. the result of a plugin select operation):

server.on('route', (route, connection, server) => {

    console.log('New route added: ' + route.path);
});

Internal events

The following logs are generated automatically by the framework. Each event can be identified by the combination of tags used.

Request logs

Emitted by the server 'request-internal' event:

Server logs

Emitted by the server 'log' event:

Plugins

Plugins provide a way to organize the application code by splitting the server logic into smaller components. Each plugin can manipulate the server and its connections through the standard server interface, but with the added ability to sandbox certain properties.

A plugin is a function with the signature function(server, options, next) where:

The plugin function must include an attributes function property with the following:

const register = function (server, options, next) {

    server.route({
        method: 'GET',
        path: '/test',
        handler: function (request, reply) {

            return reply('ok');
        }
    });

    return next();
};

register.attributes = {
    name: 'test',
    version: '1.0.0'
};

Alternatively, the name and version can be included via the pkg attribute containing the 'package.json' file for the module which already has the name and version included:

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

Requests

Incoming requests are handled by the server via routes. Each route describes an HTTP endpoint with a path, method, and other properties. The route logic is divided between static configuration, prerequisite functions and a route handler function. Routes are added via the server.route() method.

Request lifecycle

Each incoming request passes through a pre-defined list of steps, along with optional extensions:

Route configuration

The route configuration object supports the following options:

Note that the options object is deeply cloned (with the exception of bind which is shallowly copied) and cannot contain any values that are unsafe to perform deep copy on.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

// Handler in top level

const status = function (request, reply) {

    return reply('ok');
};

server.route({ method: 'GET', path: '/status', handler: status });

// Handler in config

const user = {
    cache: { expiresIn: 5000 },
    handler: function (request, reply) {

        return reply({ name: 'John' });
    }
};

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

Route options

Each route can be customized to change the default behavior of the request lifecycle using the following options:

The following documentation options are also available when adding new routes (they are not available when setting defaults):

Route public interface

When route information is returned or made available as a property, it is an object with the following:

Path parameters

Parameterized paths are processed by matching the named parameters to the content of the incoming request path at that path segment. For example, '/book/{id}/cover' will match '/book/123/cover' and request.params.id will be set to '123'. Each path segment (everything between the opening '/' and the closing '/' unless it is the end of the path) can only include one named parameter. A parameter can cover the entire segment ('/{param}') or part of the segment ('/file.{ext}'). A path parameter may only contain letters, numbers and underscores, e.g. '/{file-name}' is invalid and '/{file_name}' is valid.

An optional '?' suffix following the parameter name indicates an optional parameter (only allowed if the parameter is at the ends of the path or only covers part of the segment as in '/a{param?}/b'). For example, the route '/book/{id?}' matches '/book/' with the value of request.params.id set to an empty string ''.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const getAlbum = function (request, reply) {

    return reply('You asked for ' +
        (request.params.song ? request.params.song + ' from ' : '') +
        request.params.album);
};

server.route({
    path: '/{album}/{song?}',
    method: 'GET',
    handler: getAlbum
});

In addition to the optional ? suffix, a parameter name can also specify the number of matching segments using the * suffix, followed by a number greater than 1. If the number of expected parts can be anything, then use * without a number (matching any number of segments can only be used in the last path segment).

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const getPerson = function (request, reply) {

    const nameParts = request.params.name.split('/');
    return reply({ first: nameParts[0], last: nameParts[1] });
};

server.route({
    path: '/person/{name*2}',   // Matches '/person/john/doe'
    method: 'GET',
    handler: getPerson
});

Path matching order

The router iterates through the routing table on each incoming request and executes the first (and only the first) matching route. Route matching is done based on the combination of the request path and the HTTP verb (e.g. 'GET, 'POST'). The query is excluded from the routing logic. Requests are matched in a deterministic order where the order in which routes are added does not matter.

Routes are matched based on the specificity of the route which is evaluated at each segment of the incoming request path. Each request path is split into its segment (the parts separated by '/'). The segments are compared to the routing table one at a time and are matched against the most specific path until a match is found. If no match is found, the next match is tried.

When matching routes, string literals (no path parameter) have the highest priority, followed by mixed parameters ('/a{p}b'), parameters ('/{p}'), and then wildcard (/{p*}).

Note that mixed parameters are slower to compare as they cannot be hashed and require an array iteration over all the regular expressions representing the various mixed parameter at each routing table node.

Catch all route

If the application needs to override the default Not Found (404) error response, it can add a catch-all route for a specific method or all methods. Only one catch-all route can be defined per server connection.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const handler = function (request, reply) {

    return reply('The page was not found').code(404);
};

server.route({ method: '*', path: '/{p*}', handler: handler });

Route handler

The route handler function uses the signature function(request, reply) (NOTE: do not use a fat arrow style function for route handlers as they do not allow context binding and will cause problems when used in conjunction with server.bind) where:

const handler = function (request, reply) {

    return reply('success');
};

If the handler returns a Promise then Hapi will register a catch handler on the promise object to catch unhandled promise rejections. The handler will reply with the rejected value, wrapped in a Boom error:

const handler = function (request, reply) {

    const badPromise = () => {

        new Promise((resolve, reject) => {

            // Hapi catches this...
            throw new Error();

            // ...and this...
            return reject(new Error());
        }
    }

    // ...if you don't provide a 'catch'. The rejection will be wrapped in a Boom error.
    return badPromise().then(reply);
}

This provides a safety net for unhandled promise rejections.

Route prerequisites

It is often necessary to perform prerequisite actions before the handler is called (e.g. load required reference data from a database). The route pre option allows defining such pre-handler methods. The methods are called in order. If the pre array contains another array, those methods are called in parallel. pre can be assigned a mixed array of:

Note that prerequisites do not follow the same rules of the normal reply interface. In all other cases, calling reply() with or without a value will use the result as the response sent back to the client. In a prerequisite method, calling reply() will assign the returned value to the provided assign key. If the returned value is an error, the failAction setting determines the behavior. To force the return value as the response and skip any other prerequisites and the handler, use the reply().takeover() method.

The reason for the difference in the reply interface behavior is to allow reusing handlers and prerequisites methods interchangeably. By default, the desired behavior for a prerequisite is to retain the result value and pass it on to the next step. Errors end the lifecycle by default. While less consistent, this allows easier code reusability.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const pre1 = function (request, reply) {

    return reply('Hello');
};

const pre2 = function (request, reply) {

    return reply('World');
};

const pre3 = function (request, reply) {

    return reply(request.pre.m1 + ' ' + request.pre.m2);
};

server.route({
    method: 'GET',
    path: '/',
    config: {
        pre: [
            [
                // m1 and m2 executed in parallel
                { method: pre1, assign: 'm1' },
                { method: pre2, assign: 'm2' }
            ],
            { method: pre3, assign: 'm3' },
        ],
        handler: function (request, reply) {

            return reply(request.pre.m3 + '\n');
        }
    }
});

Request object

The request object is created internally for each incoming request. It is different from the node.js request object received from the HTTP server callback (which is available in request.raw.req). The request object methods and properties change throughout the request lifecycle.

Request properties

Each request object includes the following properties:

request.setUrl(url, [stripTrailingSlash]

Available only in 'onRequest' extension methods.

Changes the request URI before the router begins processing the request where:

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const onRequest = function (request, reply) {

    // Change all requests to '/test'
    request.setUrl('/test');
    return reply.continue();
};

server.ext('onRequest', onRequest);

To use another query string parser:

const Url = require('url');
const Hapi = require('hapi');
const Qs = require('qs');

const server = new Hapi.Server();
server.connection({ port: 80 });

const onRequest = function (request, reply) {

    const uri = request.url.href;
    const parsed = Url.parse(uri, false);
    parsed.query = Qs.parse(parsed.query);
    request.setUrl(parsed);

    return reply.continue();
};

server.ext('onRequest', onRequest);

request.setMethod(method)

Available only in 'onRequest' extension methods.

Changes the request method before the router begins processing the request where:

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const onRequest = function (request, reply) {

    // Change all requests to 'GET'
    request.setMethod('GET');
    return reply.continue();
};

server.ext('onRequest', onRequest);

request.generateResponse(source, [options])

Always available.

Returns a response which you can pass into the reply interface where:

For example it can be used inside a promise to create a response object which has a non-error code to resolve with the reply interface:

const handler = function (request, reply) {

    const result = promiseMethod().then((thing) => {

        if (!thing) {
            return request.generateResponse().code(214);
        }
        return thing;
    });
    return reply(result);
};

request.log(tags, [data, [timestamp]])

Always available.

Logs request-specific events. When called, the server emits a 'request' event which can be used by other listeners or plugins. The arguments are:

Any logs generated by the server internally will be emitted only on the 'request-internal' channel and will include the event.internal flag set to true.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80, routes: { log: true } });

server.on('request', (request, event, tags) => {

    if (tags.error) {
        console.log(event);
    }
});

const handler = function (request, reply) {

    request.log(['test', 'error'], 'Test event');
    return reply();
};

request.getLog([tags], [internal])

Always available.

Returns an array containing the events matching any of the tags specified (logical OR) where:

Note that this methods requires the route log configuration set to true.

request.getLog();
request.getLog('error');
request.getLog(['error', 'auth']);
request.getLog(['error'], true);
request.getLog(false);

request.tail([name])

Available until immediately after the 'response' event is emitted.

Adds a request tail which has to complete before the request lifecycle is complete where:

Returns a tail function which must be called when the tail activity is completed.

Tails are actions performed throughout the request lifecycle, but which may end after a response is sent back to the client. For example, a request may trigger a database update which should not delay sending back a response. However, it is still desirable to associate the activity with the request when logging it (or an error associated with it).

When all tails completed, the server emits a 'tail' event.

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const get = function (request, reply) {

    const dbTail = request.tail('write to database');

    db.save('key', 'value', () => {

        dbTail();
    });

    return reply('Success!');
};

server.route({ method: 'GET', path: '/', handler: get });

server.on('tail', (request) => {

    console.log('Request completed including db activity');
});

Request events

The request object supports the following events:

const Crypto = require('crypto');
const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const onRequest = function (request, reply) {

    const hash = Crypto.createHash('sha1');
    request.on('peek', (chunk) => {

        hash.update(chunk);
    });

    request.once('finish', () => {

        console.log(hash.digest('hex'));
    });

    request.once('disconnect', () => {

        console.error('request aborted');
    });

    return reply.continue();
};

server.ext('onRequest', onRequest);

Reply interface

The various request lifecycle events (e.g. extensions, authentication, route prerequisites, handlers) provide a reply interface as one of the function arguments. The reply interface acts as both a callback interface to return control to the framework and a response generator.

When reply() is called with an error or result response, that value is used as the response sent to the client. When reply() is called within a prerequisite, the value is saved for future use and is not used as the response. In all other places except for the handler, calling reply() will be considered an error and will abort the request lifecycle, jumping directly to the 'onPreResponse' event.

To return control to the framework within an extension or other places other than the handler, without setting a response, the method reply.continue() must be called. Except when used within an authentication strategy, or in an 'onPostHandler' or 'onPreResponse' extension, the reply.continue() must not be passed any argument or an exception is thrown.

reply([err], [result])

Concludes the handler activity by setting a response and returning control over to the framework where:

Since a request can only have one response regardless if it is an error or success, the reply() method can only result in a single response value. This means that passing both an err and result will only use the err. There is no requirement for either err or result to be (or not) an Error object. The framework will simply use the first argument if present, otherwise the second. The method supports two arguments to be compatible with the common callback pattern of error first. If a third argument is passed, an exception is thrown.

Both err and result can be set to:

const handler = function (request, reply) {

    return reply('success');
};

If the input is not an Error object, the method returns a response object which provides a set of methods to customize the response (e.g. HTTP status code, custom headers, etc.). If the input is an Error object, the method returns back the error wrapped in a Boom object.

Note that when used to return both an error and credentials in the authentication methods, reply() must be called with three arguments function(err, null, data) where data is the additional authentication information. This is the only time where a third argument is allowed (and required).

The response flow control rules apply.

// Detailed notation

const handler = function (request, reply) {

    const response = reply('success');
    response.type('text/plain');
    response.header('X-Custom', 'some-value');
};

// Chained notation

const handler = function (request, reply) {

    return reply('success')
        .type('text/plain')
        .header('X-Custom', 'some-value');
};

Note that if result is a Stream with a statusCode property, that status code will be used as the default response code.

Any value provided to reply() (including no value) will be used as the response sent back to the client. This means calling reply() with a value in an extension methods or authentication function will be considered an error and will terminate the request lifecycle. With the exception of the handler function, all other methods provide the reply.continue() method which instructs the framework to continue processing the request without setting a response.

The reply object includes the following properties:

Response object

Every response includes the following properties:

Response objects also includes the isBoom, and optional isMissing properties from boom error objects.

The response object provides the following methods:

Response Object Redirect Methods

When using the redirect() method, the response object provides these additional methods:

Permanent Temporary
Rewritable 301 302(1)
Non-rewritable 308(2) 307

Notes:

  1. Default value.
  2. Proposed code, not supported by all clients.
Response events

The response object supports the following events:

const Crypto = require('crypto');
const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const preResponse = function (request, reply) {

    const response = request.response;
    if (response.isBoom) {
        return reply();
    }

    const hash = Crypto.createHash('sha1');
    response.on('peek', (chunk) => {

        hash.update(chunk);
    });

    response.once('finish', () => {

        console.log(hash.digest('hex'));
    });

    return reply.continue();
};

server.ext('onPreResponse', preResponse);

Error response

hapi uses the boom error library for all its internal error generation. boom provides an expressive interface to return HTTP errors. Any error returned via the reply interface is converted to a boom object and defaults to status code 500 if the error is not a boom object.

When the error is sent back to the client, the response contains a JSON object with the statusCode, error, and message keys.

const Hapi = require('hapi');
const Boom = require('boom');

const server = new Hapi.Server();

server.route({
    method: 'GET',
    path: '/badRequest',
    handler: function (request, reply) {

        return reply(Boom.badRequest('Unsupported parameter'));
    }
});

server.route({
    method: 'GET',
    path: '/internal',
    handler: function (request, reply) {

        return reply(new Error('unexpect error'));
    }
});
Error transformation

Errors can be customized by changing their output content. The boom error object includes the following properties:

It also supports the following method:

const Boom = require('boom');

const handler = function (request, reply) {

    const error = Boom.badRequest('Cannot feed after midnight');
    error.output.statusCode = 499;    // Assign a custom error code
    error.reformat();

    error.output.payload.custom = 'abc_123'; // Add custom key

    return reply(error);
});

When a different error representation is desired, such as an HTML page or a different payload format, the 'onPreResponse' extension point may be used to identify errors and replace them with a different response object.

const Hapi = require('hapi');
const Vision = require('vision');
const server = new Hapi.Server();
server.register(Vision, (err) => {
    server.views({
        engines: {
            html: require('handlebars')
        }
  });
});
server.connection({ port: 80 });

const preResponse = function (request, reply) {

    const response = request.response;
    if (!response.isBoom) {
        return reply.continue();
    }

    // Replace error with friendly HTML

      const error = response;
      const ctx = {
          message: (error.output.statusCode === 404 ? 'page not found' : 'something went wrong')
      };

      return reply.view('error', ctx).code(error.output.statusCode);
};

server.ext('onPreResponse', preResponse);

Flow control

When calling reply(), the framework waits until process.nextTick() to continue processing the request and transmit the response. This enables making changes to the returned response object before the response is sent. This means the framework will resume as soon as the handler method exits. To suspend this behavior, the returned response object supports the following methods:

const handler = function (request, reply) {

    const response = reply('success').hold();

    setTimeout(() => {

        response.send();
    }, 1000);
};

reply.continue([result])

Returns control back to the framework without ending the request lifecycle, where:

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

const onRequest = function (request, reply) {

    // Change all requests to '/test'
    request.setUrl('/test');
    return reply.continue();
};

server.ext('onRequest', onRequest);

reply.entity(options)

Sets the response 'ETag' and 'Last-Modified' headers and checks for any conditional request headers to decide if the response is going to qualify for an HTTP 304 (Not Modified). If the entity values match the request conditions, reply.entity() returns control back to the framework with a 304 response. Otherwise, it sets the provided entity headers and returns null, where:

Returns a response object if the reply is unmodified or null if the response has changed. If null is returned, the developer must call reply() to continue execution. If the response is not null, the developer must not call reply().

const Hapi = require('hapi');
const server = new Hapi.Server();
server.connection({ port: 80 });

server.route({
    method: 'GET',
    path: '/',
    config: {
        cache: { expiresIn: 5000 },
        handler: function (request, reply) {

            const response = reply.entity({ etag: 'abc' });
            if (response) {
                response.header('X', 'y');
                return;
            }

            return reply('ok');
        }
    }
});

reply.close([options])

Concludes the handler activity by returning control over to the router and informing the router that a response has already been sent back directly via request.raw.res and that no further response action is needed. Supports the following optional options:

No return value.

The response flow control rules do not apply.

reply.redirect(uri)

Redirects the client to the specified uri. Same as calling reply().redirect(uri).

Returns a response object.

The response flow control rules apply.

const handler = function (request, reply) {

    return reply.redirect('http://example.com');
};

reply.response(result)

Shorthand for calling reply(null, result), causes a reply with the response set to result.

const handler = function (request, reply) {

    return reply.response('result');
};

reply.state(name, value, [options])

Sets a cookie on the response (see response object methods).

const handler = function (request, reply) {

    reply.state('cookie-name', 'value');
    return reply.response('result');
};

reply.unstate(name, [options])

Clears a cookie on the response (see response object methods).

const handler = function (request, reply) {

    reply.unstate('cookie-name');
    return reply.response('result');
};

Changing to a permanent or non-rewritable redirect is also available see response object redirect for more information.