Skip to content

Hapi plugin

Roman edited this page Mar 27, 2019 · 6 revisions

Hapi plugin

Create plugin/rateLimitRedis.js script with limiter and options required for your application.

'use strict';

const Boom = require('boom');
const redis = require('redis');
const {RateLimiterRedis} = require('rate-limiter-flexible');

const internals = {
  pluginName: 'rateLimitRedisPlugin',
};

internals.redisClient = redis.createClient({
  host: 'localhost',
  port: 6379,
  enable_offline_queue: false,
});

internals.rateLimiter = new RateLimiterRedis({
  redis: internals.redisClient,
  keyPrefix: 'hapi-plugin',
  points: 10, // 10 requests
  duration: 1, // per 1 second by IP
});

module.exports = {
  name: internals.pluginName,
  version: '1.0.0',
  register: function (server) {
    server.ext('onPreAuth', async (request, h) => {
      try {
        await internals.rateLimiter.consume(request.info.remoteAddress);
        return h.continue;
      } catch (rej) {
        let error;
        if (rej instanceof Error) {
          // If some Redis error and `insuranceLimiter` is not set
          error = Boom.internal('Try later');
        } else {
          // Not enough points to consume
          error = Boom.tooManyRequests('Rate limit exceeded');
          error.output.headers['Retry-After'] = Math.round(rej.msBeforeNext / 1000) || 1;
        }

        return error;
      }
    });
  }
};

See all options here

onPreAuth event is used to rate limit requests for all application routes. Depending on requirements you may use onPostAuth, onPostHandler, etc (read about request lifecycle in official hapi docs)

Import created plugin and register it

'use strict';

const Hapi = require('hapi');

const server = Hapi.server({
  port: 3000,
  host: 'localhost'
});

server.route({
  method: 'GET',
  path: '/',
  handler: (request, h) => {

    return 'Hello, world!';
  }
});

const init = async () => {
  await server.register(require('./plugin/rateLimitRedis'));
  await server.start();
};

process.on('unhandledRejection', (err) => {
  process.exit(1);
});

init();

Mongo, Memcached, MySQL or any other limiter from this package can be used with the same approach.