A powerful rate limiting plugin for hapi.js that helps prevent brute-force attacks and API abuse.
- 🚀 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
- Node.js: >= 18
- hapi: >= 21
- Redis: A running Redis instance or Redis Cluster
npm install @perdieminc/hapi-rate-limitorconst 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();The plugin supports multiple ways to configure a standard Redis connection:
await server.register({
plugin: require('@perdieminc/hapi-rate-limitor'),
options: {
redis: 'redis://localhost:6379'
}
});await server.register({
plugin: require('@perdieminc/hapi-rate-limitor'),
options: {
redis: {
host: 'localhost',
port: 6379,
password: 'your-password',
db: 0
}
}
});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
}
});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.
| 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 |
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'
}
});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 };
}
}
});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' };
}
}
});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
]
}
});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
};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'
});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';
}
}
});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;
}
}
});await server.register({
plugin: require('@perdieminc/hapi-rate-limitor'),
options: {
redis: 'redis://localhost:6379',
extensionPoint: 'onPreAuth' // Rate limit before authentication
}
});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}`);
});
}
}
}
}
});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 allowedX-Rate-Limit-Remaining: Number of requests remaining in the current windowX-Rate-Limit-Reset: Unix timestamp (in seconds) when the rate limit resets
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"
}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;
}
}
});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
}
});npm testMIT © Marcus Pöhls
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:
- ⭐ Starring the original repository
- 💖 Supporting Future Studio
- 🤝 Contributing back improvements to the upstream project
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.
This plugin uses:
- ioredis for Redis connectivity
- async-ratelimiter for rate limiting logic
- @supercharge/request-ip for IP detection
Made with ❤️ for the hapi.js community
When you're ready to release a new version:
-
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. -
Push the tag (triggers automatic publish):
git push origin master --follow-tags
-
GitHub Actions automatically:
- ✅ Runs all tests
- ✅ Publishes to npm
- ✅ Creates a GitHub Release with auto-generated notes
If you need to publish manually without the automation:
npm publish --access public# Delete local tag
git tag -d v4.0.2
# Delete remote tag
git push origin --delete v4.0.2To get the latest updates from the original repository:
git fetch upstream
git merge upstream/master
git push origin masterSee the upstream repository for the latest changes.