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

ratli

v1.2.2

Published

A Hapi.js plugin for rate limiting.

Readme

Quality Gate Status Bugs Code Smells Duplicated Lines (%) Coverage Known Vulnerabilities

Ratli

A Hapi.js plugin for flexible rate limiting with support for multiple identification methods, allow/block lists, and automatic header injection.

Installation

npm install ratli

Features

  • Multiple Identification Methods: Rate limit by IP address or API key
  • Flexible Allow/Block Lists: Bypass or block specific clients
  • IETF Standard Headers: Automatically adds RateLimit-* headers to responses
  • Memory Storage: Built-in in-memory storage with automatic cleanup and configurable limits
  • Event Callbacks: Monitor rate limit violations and blocks with onRateLimit and onBlock hooks
  • X-Forwarded-For Support: Trust proxy headers with configurable sources and IP validation
  • 429 & 403 Responses: Standard HTTP status codes for rate limiting and blocking
  • Status Checking: Query current rate limit status without consuming points
  • TypeScript Support: Full type definitions included

Usage

Basic Example

By default, the plugin rate limits by IP address with 100 requests per 60 seconds:

import Hapi from '@hapi/hapi'
import Ratli from 'ratli'

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

await server.register({ plugin: Ratli })

server.route({
  method: 'GET',
  path: '/api/users',
  handler: () => ({ users: [] })
})

await server.start()
// Requests limited to 100 per minute per IP address
// Response includes: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset

Custom Rate Limits

Configure the number of requests and time window:

await server.register({
  plugin: Ratli,
  options: {
    rateLimit: {
      points: 10,      // 10 requests
      duration: 60,    // per 60 seconds
      blockDuration: 300  // block for 5 minutes after exceeding limit
    }
  }
})
// Limits clients to 10 requests per minute

API Key-Based Rate Limiting

Rate limit by API key instead of IP address:

await server.register({
  plugin: Ratli,
  options: {
    ip: false,
    key: {
      headerName: 'x-api-key',
      queryParamName: 'api_key'
    },
    rateLimit: {
      points: 1000,
      duration: 3600  // 1000 requests per hour per API key
    }
  }
})

server.route({
  method: 'GET',
  path: '/api/data',
  handler: () => ({ data: [] })
})
// Rate limited by x-api-key header or api_key query parameter

Configuration

Global Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | ip | boolean \| object | true | Enable IP-based rate limiting or configure IP options | | key | boolean \| object | false | Enable API key-based rate limiting or configure key options | | storage | object | { type: 'memory', options: { maxSize: 10000, cleanupInterval: 60000 } } | Storage configuration | | rateLimit | object | See below | Rate limit configuration | | onRateLimit | function | undefined | Callback fired when rate limit is exceeded: (identifier, request) => void | | onBlock | function | undefined | Callback fired when a client is blocked: (identifier, request) => void |

Rate Limit Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | points | number | 100 | Number of requests allowed in the time window | | duration | number | 60 | Time window in seconds | | blockDuration | number | 300 | How long to block after exceeding limit (seconds) |

Storage Options

When configuring storage.options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | maxSize | number | 10000 | Maximum number of entries to store (LRU eviction when exceeded) | | cleanupInterval | number | 60000 | Interval in milliseconds to clean up expired entries |

IP Options

When ip is an object, you can configure:

| Option | Type | Default | Description | |--------|------|---------|-------------| | allowList | string[] | [] | IPs that bypass rate limiting | | blockList | string[] | [] | IPs that are always blocked (403) | | allowXForwardedFor | boolean | false | Trust X-Forwarded-For header | | allowXForwardedForFrom | string[] | [] | Only trust X-Forwarded-For from these IPs |

API Key Options

When key is an object, you can configure:

| Option | Type | Default | Description | |--------|------|---------|-------------| | allowList | string[] | [] | API keys that bypass rate limiting | | blockList | string[] | [] | API keys that are always blocked (403) | | headerName | string | 'x-api-key' | Header name to check for API key | | queryParamName | string | 'api_key' | Query parameter name for API key | | fallbackToIpOnMissingKey | boolean | true | Fallback to IP-based limiting when no key is provided (only if ip is enabled) |

Examples

Allow Lists - Premium Users

Bypass rate limiting for specific API keys:

await server.register({
  plugin: Ratli,
  options: {
    ip: false,
    key: {
      allowList: ['premium-key-1', 'premium-key-2'],
      headerName: 'x-api-key'
    },
    rateLimit: {
      points: 100,
      duration: 60
    }
  }
})
// Premium keys in allowList have unlimited requests
// Other keys limited to 100 requests per minute

Block Lists - Banned IPs

Block specific IP addresses:

await server.register({
  plugin: Ratli,
  options: {
    ip: {
      blockList: ['192.168.1.100', '10.0.0.50']
    }
  }
})
// Blocked IPs receive 403 Forbidden immediately

X-Forwarded-For with Trusted Proxies

Trust X-Forwarded-For header only from your load balancer:

await server.register({
  plugin: Ratli,
  options: {
    ip: {
      allowXForwardedFor: true,
      allowXForwardedForFrom: ['10.0.1.1', '10.0.1.2']  // Your load balancers
    }
  }
})
// Uses X-Forwarded-For only when request comes from trusted IPs

Combined IP and API Key

Rate limit by IP, but allow API key overrides:

await server.register({
  plugin: Ratli,
  options: {
    ip: true,  // Primary rate limiting by IP
    key: {     // API keys can bypass if in allowList
      allowList: ['trusted-service-key']
    }
  }
})
// Regular users: rate limited by IP
// Requests with trusted-service-key: unlimited

Key-Only Mode Without IP Fallback

Require API keys and avoid falling back to IP when keys are missing:

await server.register({
  plugin: Ratli,
  options: {
    ip: false,
    key: {
      headerName: 'x-api-key',
      queryParamName: 'api_key',
      fallbackToIpOnMissingKey: false
    },
    rateLimit: {
      points: 500,
      duration: 3600
    }
  }
})
// Requests must include an API key to be rate limited
// Missing keys are not rate limited in this configuration

Different Limits for Different Tiers

You can register multiple instances or use allow lists strategically:

// Basic tier: IP-based
await server.register({
  plugin: Ratli,
  options: {
    ip: true,
    rateLimit: {
      points: 100,
      duration: 3600  // 100 requests per hour
    }
  }
})

// For premium users, add their API keys to allowList
// They bypass the IP-based rate limit

Response Headers

The plugin automatically adds IETF standard rate limit headers to all responses:

RateLimit-Limit: 100
RateLimit-Remaining: 95
RateLimit-Reset: 1735301400

Note: RateLimit-Reset is a Unix timestamp (seconds since epoch).

When rate limit is exceeded (429 response):

RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 1735301400
Retry-After: 45

Retry-After reflects the remaining block window when blockDuration > 0; otherwise, it reflects the time until the current window resets.

Block Duration Example

When a client exceeds the limit and blockDuration is set (e.g., 120 seconds), subsequent requests during the block window receive:

RateLimit-Limit: 10
RateLimit-Remaining: 0
RateLimit-Reset: 1735301700
Retry-After: 120

As time progresses within the block window, Retry-After decreases accordingly until the block expires.

HTTP Status Codes

  • 200 OK: Request successful, under rate limit
  • 429 Too Many Requests: Rate limit exceeded
  • 403 Forbidden: Client is in a block list

Priority of Identification Methods

Identification resolution works as follows:

  • IP: Used when ip is enabled (true or an object).
  • API Key: Used when ip is disabled and key is enabled (boolean or object). If both are enabled, IP takes precedence.
  • Fallback: If no API key is present, fallback to IP occurs only when ip is not disabled and key.fallbackToIpOnMissingKey is true. Otherwise, the request is not rate limited.

Event Callbacks

Monitor rate limiting events with callback functions:

onRateLimit Callback

Called when a client exceeds their rate limit:

await server.register({
  plugin: Ratli,
  options: {
    rateLimit: {
      points: 100,
      duration: 60
    },
    onRateLimit: (identifier, request) => {
      console.log(`Rate limit exceeded for ${identifier} on ${request.path}`)
      // Log to your monitoring system, send alerts, etc.
    }
  }
})

onBlock Callback

Called when a client is blocked (from allow/block lists):

await server.register({
  plugin: Ratli,
  options: {
    ip: {
      blockList: ['192.168.1.100']
    },
    onBlock: (identifier, request) => {
      console.log(`Blocked request from ${identifier} to ${request.path}`)
      // Track malicious IPs, send alerts, etc.
    }
  }
})

Storage Configuration

Configure memory storage limits and cleanup:

await server.register({
  plugin: Ratli,
  options: {
    storage: {
      type: 'memory',
      options: {
        maxSize: 5000,        // Store max 5000 entries (LRU eviction)
        cleanupInterval: 30000  // Clean up expired entries every 30 seconds
      }
    },
    rateLimit: {
      points: 100,
      duration: 60
    }
  }
})

Testing Support

The plugin exposes methods for testing and monitoring:

Reset Storage

// In your tests
await server.plugins.ratli.reset()
// All rate limit counters are cleared

Check Rate Limit Status

// Check current status without consuming points
const status = await server.plugins.ratli.getStatus('ip:192.168.1.1')
// Returns: { remainingPoints: 95, resetTime: 1735301400000, isBlocked: false }
// Returns null if no entry exists

TypeScript

The plugin includes full TypeScript definitions:

import { Server } from '@hapi/hapi'
import Ratli, { RatliPluginOptions } from 'ratli'

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

const options: RatliPluginOptions = {
  ip: {
    allowList: ['127.0.0.1'],
    allowXForwardedFor: true
  },
  rateLimit: {
    points: 1000,
    duration: 3600,
    blockDuration: 300
  }
}

await server.register({ plugin: Ratli, options })

License

MIT