Hapi.js is a framework for building web applications. This post contains all the essentials for a hot start. Unfortunately, the author is not a writer at all, so there will be a lot of code and few words.
MVP
Put a bunch of dependencies:
npm i @hapi/hapi @hapi/boom filepaths hapi-boom-decorators
- hapi / hapi - actually, our server
- hapi / boom - module for generating standard answers
- hapi-boom-decorators - helper for hapi / boom
- filepaths - a utility that recursively reads folders
Create a folder structure and a bunch of start files:

In ./src/routes/ we add a description of api endpoints, 1 file - 1 endpoint:
./src/server.js - the module that exports the server itself.
In ./server.js all we do is call createServer ()
#!/usr/bin/env node const createServer = require('./src/server'); createServer();
We launch
node server.js
And check:
curl http://127.0.0.1:3030/ {"result":"ok","message":"Hello World!"} curl http://127.0.0.1:3030/test {"statusCode":404,"error":"Not Found","message":"Not Found"}
In the wild
In a real project, at a minimum, we need a database, a logger, authorization, error handling, and much more.
Add sequelize
ORM sequelize is connected as a module:
... const Sequelize = require('sequelize'); ... await server.register([ ... { plugin: require('hapi-sequelizejs'), options: [ { name: config.db.database,
The database becomes available inside the route through a call:
async function response(request) { const model = request.getModel('_', '_'); }
Inject additional modules into the request
It is necessary to intercept the "onRequest" event, inside which we will inject the config and logger into the request object:
... const Logger = require('./libs/Logger'); ... async function createServer(logLVL=config.logLVL) { ... const logger = new Logger(logLVL, 'my-hapi-app'); ... server.ext({ type: 'onRequest', method: async function (request, h) { request.server.config = Object.assign({}, config); request.server.logger = logger; return h.continue; } }); ... }
After that, inside the request handler, we will have access to the config, logger, and database, without the need to additionally include something in the module body:
Thus, the request handler at the input will receive everything necessary for its processing, and it will not be necessary to include the same modules each time from time to time.
Login
Authorization in hapi is made in the form of modules.
... const AuthBearer = require('hapi-auth-bearer-token'); ... async function createServer(logLVL=config.logLVL) { ... await server.register([ AuthBearer, ... ]); server.auth.strategy('token', 'bearer-access-token', {
And also inside the route you need to specify which type of authorization to use:
module.exports = { method: 'GET', path: '/', auth: 'token',
If several types of authorization are used:
auth: { strategies: ['token1', 'token2', 'something_else'] },
Error processing
By default, boom generates errors in a standard way, often these answers need to be wrapped in their own format.
server.ext('onPreResponse', function (request, h) {
Data schemas
This is a small but very important topic. Data schemes allow you to check the validity of the request and the correctness of the response. How well you describe these schemes, so will swagger and autotests be of such quality.
All data schemes are described through joi. Let's make an example for user authorization:
const Joi = require('@hapi/joi'); const Boom = require('boom'); async function response(request) {
Testing:
curl -X GET "http://localhost:3030/auth?login=pupkin@gmail.com&password=12345"

Now send instead of mail, only login:
curl -X GET "http://localhost:3030/auth?login=pupkin&password=12345"

If the answer does not match the response scheme, then the server will also fall into a 500 error.
If the project began to process more than 1 request per hour, it may be necessary to limit the verification of responses, as verification is a resource-intensive operation. There is a parameter for this: "sample"
module.exports = { method: 'GET', path: '/auth', options: { handler: response, validate: { query: requestScheme }, response: { sample: 50, schema: responseScheme } } };
In this form, only 50% of requests will be validated responses.
It is very important to describe the default values and examples, in the future they will be used to generate documentation and autotests.
Swagger / OpenAPI
We need a bunch of additional modules:
npm i hapi-swagger @hapi/inert @hapi/vision
We connect them to server.js
... const Inert = require('@hapi/inert'); const Vision = require('@hapi/vision'); const HapiSwagger = require('hapi-swagger'); const Package = require('../package'); ... const swaggerOptions = { info: { title: Package.name + ' API Documentation', description: Package.description }, jsonPath: '/documentation.json', documentationPath: '/documentation', schemes: ['https', 'http'], host: config.swaggerHost, debug: true }; ... async function createServer(logLVL=config.logLVL) { ... await server.register([ ... Inert, Vision, { plugin: HapiSwagger, options: swaggerOptions }, ... ]); ... });
And in each route you need to put the tag "api":
module.exports = { method: 'GET', path: '/auth', options: { handler: response, tags: [ 'api' ],
Now at http: // localhost: 3030 / documentation a web-face with documentation will be available, and at http: // localhost: 3030 / documentation.json .json a description.

Auto Test Generation
If we have qualitatively described the request and response schemes, prepared a seed database corresponding to the examples described in the request examples, then, according to well-known schemes, you can automatically generate requests and check server response codes.
For example, in GET: / auth login and password parameters are expected, we will take them from the examples that we specified in the diagram:
const requestScheme =Joi.object({ login: Joi.string().email().required().example('pupkin@gmail.com'), password: Joi.string().required().example('12345') });
And if the server responds with HTTP-200-OK, then we will assume that the test has passed.
Unfortunately, there was no ready-made suitable module; you have to talk a little bit:
Do not forget about the dependencies:
npm i request-promise mocha sync-request
And about package.json
... "scripts": { "test": "mocha", "dbinit": "node ./scripts/dbInit.js" }, ...
We check:
npm test

And if the lock is some kind of data scheme, or the answer does not match the scheme:

And do not forget that sooner or later the tests will become sensitive to the data in the database. Before running the tests, you need to at least wipe the database.
Entire Sources