Getting Started
Installation
Install the core package:
npm install universal-rate-limitOr with your preferred package manager:
pnpm add universal-rate-limit
yarn add universal-rate-limitIf you're using a framework, install the corresponding middleware package instead — it includes the core as a dependency:
npm install @universal-rate-limit/express
npm install @universal-rate-limit/fastify
npm install @universal-rate-limit/hono
npm install @universal-rate-limit/nextjsFor distributed deployments, add the Redis store:
npm install @universal-rate-limit/redisBasic Usage
The rateLimit function creates a limiter that accepts a Web Standard Request and returns a RateLimitResult:
import { rateLimit } from 'universal-rate-limit';
const limiter = rateLimit({
windowMs: 60_000, // 1 minute window
limit: 60 // 60 requests per window
});
// In your request handler:
const result = await limiter(request);
if (result.limited) {
return new Response('Too Many Requests', {
status: 429,
headers: result.headers
});
}
// Continue processing...Options
All options are optional with sensible defaults:
| Option | Type | Default | Description |
|---|---|---|---|
windowMs | number | 60_000 | Time window in milliseconds |
limit | number | (req) => number | 60 | Max requests per window (can be async) |
algorithm | 'fixed-window' | 'sliding-window' | 'fixed-window' | Rate limiting algorithm |
headers | 'draft-7' | 'draft-6' | 'draft-7' | IETF rate limit headers version |
store | Store | MemoryStore | Storage backend (Redis, custom) |
keyGenerator | (req) => string | IP-based | Extract client identifier |
skip | (req) => boolean | undefined | Skip rate limiting for certain requests |
handler | (req, result) => Response | undefined | Custom 429 response handler |
message | string | object | function | 'Too Many Requests' | Response body when limited |
statusCode | number | 429 | HTTP status code when limited |
passOnStoreError | boolean | false | Fail open if the store errors |
Sliding Window
The sliding-window algorithm provides smoother rate limiting by weighting the previous window's hits:
const limiter = rateLimit({
windowMs: 60_000,
limit: 100,
algorithm: 'sliding-window'
});Custom Key Generator
By default, the client IP is extracted from common proxy headers (x-forwarded-for, x-real-ip, cf-connecting-ip, fly-client-ip). Override this for custom logic:
const limiter = rateLimit({
keyGenerator: request => {
// Rate limit by API key
return request.headers.get('x-api-key') ?? '127.0.0.1';
}
});Dynamic Limits
The limit option accepts a function for per-request limits:
const limiter = rateLimit({
limit: async request => {
const apiKey = request.headers.get('x-api-key');
if (apiKey === 'premium') return 1000;
return 60;
}
});Skip Requests
Bypass rate limiting for certain requests:
const limiter = rateLimit({
skip: request => {
return new URL(request.url).pathname === '/health';
}
});Custom Response
Customize the 429 response body. A Retry-After header is included automatically on all 429 responses.
const limiter = rateLimit({
message: { error: 'Rate limit exceeded' },
statusCode: 429
});Or use a handler function for full control:
const limiter = rateLimit({
handler: (request, result) => {
return new Response(JSON.stringify({ error: 'Too many requests', reset: result.resetTime }), { status: 429, headers: { 'Content-Type': 'application/json' } });
}
});