npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@perdieminc/hapi-rate-limitor

v4.0.2

Published

Rate limiting for hapi/hapi.js to prevent brute-force attacks

Readme

@perdieminc/hapi-rate-limitor

NPM Version Node.js Version

A powerful rate limiting plugin for hapi.js that helps prevent brute-force attacks and API abuse.

Features

  • 🚀 Custom Redis Support: Works with both standard Redis instances and Redis Cluster
  • 🔒 Flexible Rate Limiting: Global, route-specific, and user-specific limits
  • 🎯 IP-based & User-based: Rate limit by IP address or authenticated user
  • 📊 Response Headers: Automatic rate limit headers (X-Rate-Limit-*)
  • 🎨 Custom Views: Render custom views when rate limit is exceeded
  • Event Emitters: Hook into rate limiting events
  • 🔧 Highly Configurable: Whitelist IPs, skip routes, custom extension points

Requirements

  • Node.js: >= 18
  • hapi: >= 21
  • Redis: A running Redis instance or Redis Cluster

Installation

npm install @perdieminc/hapi-rate-limitor

Quick Start

const Hapi = require('@hapi/hapi');

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

await server.register({
  plugin: require('@perdieminc/hapi-rate-limitor'),
  options: {
    redis: 'redis://localhost:6379',
    max: 60,                    // 60 requests
    duration: 60 * 1000,        // per 60 seconds
    namespace: 'my-app-limiter'
  }
});

await server.start();

Redis Configuration

Standard Redis Instance

The plugin supports multiple ways to configure a standard Redis connection:

Connection String

await server.register({
  plugin: require('@perdieminc/hapi-rate-limitor'),
  options: {
    redis: 'redis://localhost:6379'
  }
});

Configuration Object

await server.register({
  plugin: require('@perdieminc/hapi-rate-limitor'),
  options: {
    redis: {
      host: 'localhost',
      port: 6379,
      password: 'your-password',
      db: 0
    }
  }
});

Custom Redis Instance

You can pass your own pre-configured Redis instance (useful for connection pooling or custom configurations):

const Redis = require('ioredis');

const redis = new Redis({
  host: 'localhost',
  port: 6379,
  password: 'your-password',
  retryStrategy: (times) => {
    return Math.min(times * 50, 2000);
  }
});

await server.register({
  plugin: require('@perdieminc/hapi-rate-limitor'),
  options: {
    redis: redis  // Pass your custom Redis instance
  }
});

Redis Cluster Support

The plugin fully supports Redis Cluster configurations:

const Redis = require('ioredis');

const cluster = new Redis.Cluster([
  { host: 'localhost', port: 7000 },
  { host: 'localhost', port: 7001 },
  { host: 'localhost', port: 7002 }
], {
  redisOptions: {
    password: 'your-cluster-password'
  }
});

await server.register({
  plugin: require('@perdieminc/hapi-rate-limitor'),
  options: {
    redis: cluster  // Pass your Redis Cluster instance
  }
});

Note: When passing a custom Redis instance or cluster, the plugin will not automatically connect or disconnect. You are responsible for managing the connection lifecycle.

Configuration Options

Plugin Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | enabled | Boolean | true | Enable/disable rate limiting globally | | redis | String/Object/Redis | required | Redis connection string, config object, or Redis instance | | max | Number | 60 | Maximum number of requests allowed | | duration | Number | 60000 | Time window in milliseconds (default: 60 seconds) | | namespace | String | 'hapi-rate-limitor' | Redis key namespace | | userAttribute | String | 'id' | User identifier attribute in request.auth.credentials | | userLimitAttribute | String | 'rateLimit' | User-specific rate limit attribute | | ipWhitelist | Array | [] | Array of whitelisted IP addresses | | extensionPoint | String | 'onPostAuth' | Hapi extension point for rate limiting | | view | String | undefined | Path to custom view for rate limit exceeded | | skip | Function | () => false | Function to skip rate limiting for specific requests | | getIp | Function | undefined | Custom function to extract IP address |

Usage Examples

Basic Rate Limiting

await server.register({
  plugin: require('@perdieminc/hapi-rate-limitor'),
  options: {
    redis: 'redis://localhost:6379',
    max: 100,                   // 100 requests
    duration: 60 * 1000,        // per minute
    namespace: 'my-api'
  }
});

Route-Specific Limits

You can set different rate limits for specific routes:

server.route({
  method: 'POST',
  path: '/login',
  options: {
    plugins: {
      'hapi-rate-limitor': {
        max: 5,                 // Only 5 login attempts
        duration: 5 * 60 * 1000 // per 5 minutes
      }
    },
    handler: async (request, h) => {
      return { success: true };
    }
  }
});

Disable Rate Limiting for Specific Routes

server.route({
  method: 'GET',
  path: '/health',
  options: {
    plugins: {
      'hapi-rate-limitor': {
        enabled: false  // No rate limiting for health checks
      }
    },
    handler: async (request, h) => {
      return { status: 'ok' };
    }
  }
});

IP Whitelist

await server.register({
  plugin: require('@perdieminc/hapi-rate-limitor'),
  options: {
    redis: 'redis://localhost:6379',
    ipWhitelist: [
      '127.0.0.1',
      '::1',
      '10.0.0.0/8'  // Internal network
    ]
  }
});

User-Specific Rate Limits

For authenticated users, you can set per-user rate limits:

// In your authentication handler
request.auth.credentials = {
  id: 'user-123',
  rateLimit: 1000  // This user gets 1000 requests per duration
};

Custom View for Rate Limit Exceeded

await server.register([
  {
    plugin: require('@hapi/vision')
  },
  {
    plugin: require('@perdieminc/hapi-rate-limitor'),
    options: {
      redis: 'redis://localhost:6379',
      view: 'rate-limit-exceeded'  // Render this view when exceeded
    }
  }
]);

server.views({
  engines: { html: require('handlebars') },
  path: 'views'
});

Skip Rate Limiting Conditionally

await server.register({
  plugin: require('@perdieminc/hapi-rate-limitor'),
  options: {
    redis: 'redis://localhost:6379',
    skip: (request) => {
      // Skip rate limiting for admin users
      return request.auth.credentials?.role === 'admin';
    }
  }
});

Custom IP Detection

await server.register({
  plugin: require('@perdieminc/hapi-rate-limitor'),
  options: {
    redis: 'redis://localhost:6379',
    getIp: (request) => {
      // Custom IP extraction (e.g., behind a proxy)
      return request.headers['x-real-ip'] ||
             request.headers['x-forwarded-for']?.split(',')[0] ||
             request.info.remoteAddress;
    }
  }
});

Custom Extension Point

await server.register({
  plugin: require('@perdieminc/hapi-rate-limitor'),
  options: {
    redis: 'redis://localhost:6379',
    extensionPoint: 'onPreAuth'  // Rate limit before authentication
  }
});

Rate Limit Events

The plugin emits events that you can listen to:

await server.register({
  plugin: require('@perdieminc/hapi-rate-limitor'),
  options: {
    redis: 'redis://localhost:6379',
    emitter: {
      on: (event, callback) => {
        if (event === 'rate-limit:exceeded') {
          callback((request) => {
            console.log(`Rate limit exceeded for ${request.info.remoteAddress}`);
          });
        }

        if (event === 'rate-limit:in-quota') {
          callback((request) => {
            console.log(`Request within quota: ${request.path}`);
          });
        }
      }
    }
  }
});

Response Headers

The plugin automatically adds rate limit headers to all responses:

X-Rate-Limit-Limit: 60
X-Rate-Limit-Remaining: 59
X-Rate-Limit-Reset: 1639584000
  • X-Rate-Limit-Limit: Maximum number of requests allowed
  • X-Rate-Limit-Remaining: Number of requests remaining in the current window
  • X-Rate-Limit-Reset: Unix timestamp (in seconds) when the rate limit resets

Error Response

When the rate limit is exceeded, the plugin returns a 429 Too Many Requests response:

{
  "statusCode": 429,
  "error": "Too Many Requests",
  "message": "You have exceeded the request limit"
}

Advanced Configuration

Complete Example with All Options

const Redis = require('ioredis');

const redis = new Redis.Cluster([
  { host: 'redis-node-1', port: 7000 },
  { host: 'redis-node-2', port: 7001 },
  { host: 'redis-node-3', port: 7002 }
]);

await server.register({
  plugin: require('@perdieminc/hapi-rate-limitor'),
  options: {
    enabled: true,
    redis: redis,
    max: 100,
    duration: 60 * 1000,
    namespace: 'my-app-rate-limiter',
    userAttribute: 'userId',
    userLimitAttribute: 'maxRequests',
    ipWhitelist: ['127.0.0.1', '::1'],
    extensionPoint: 'onPostAuth',
    view: 'rate-limit-exceeded',
    skip: (request) => {
      return request.path.startsWith('/public');
    },
    getIp: (request) => {
      return request.headers['x-forwarded-for']?.split(',')[0] ||
             request.info.remoteAddress;
    }
  }
});

TypeScript Support

The plugin includes TypeScript definitions:

import { Server } from '@hapi/hapi';
import * as RateLimitor from '@perdieminc/hapi-rate-limitor';

const server = new Server({ port: 3000 });

await server.register({
  plugin: RateLimitor,
  options: {
    redis: 'redis://localhost:6379',
    max: 60,
    duration: 60000
  }
});

Testing

npm test

License

MIT © Marcus Pöhls

Acknowledgments

This package is a fork of the hapi-rate-limitor created by Marcus Pöhls and the Future Studio team.

Original Repository: futurestudio/hapi-rate-limitor

We are grateful for their work and contributions to the hapi.js ecosystem. This fork maintains compatibility with the original while adding enhanced documentation and examples, particularly around Redis Cluster support.

If you find this package useful, please consider:

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

For changes that could benefit the broader community, consider contributing to the original repository as well.

Credits

This plugin uses:


Made with ❤️ for the hapi.js community

Development & Release

Creating a New Release

When you're ready to release a new version:

  1. Update version and create tag (automatically):

    npm version patch  # For bug fixes (4.0.1 -> 4.0.2)
    npm version minor  # For new features (4.0.1 -> 4.1.0)
    npm version major  # For breaking changes (4.0.1 -> 5.0.0)

    This automatically updates package.json, creates a commit, and creates a git tag.

  2. Push the tag (triggers automatic publish):

    git push origin master --follow-tags
  3. GitHub Actions automatically:

    • ✅ Runs all tests
    • ✅ Publishes to npm
    • ✅ Creates a GitHub Release with auto-generated notes

Manual Release (if needed)

If you need to publish manually without the automation:

npm publish --access public

Delete a Tag (if you made a mistake)

# Delete local tag
git tag -d v4.0.2

# Delete remote tag
git push origin --delete v4.0.2

Syncing with Upstream

To get the latest updates from the original repository:

git fetch upstream
git merge upstream/master
git push origin master

See the upstream repository for the latest changes.