
Web-standards-based rate limiting with pluggable stores and framework middleware.
Zero dependencies. Works everywhere.
Why universal-rate-limit?
Most rate limiters are tied to a single framework or runtime. universal-rate-limit is built on the Web Standards Request/Response API, so the same core works on Node.js, Bun, Deno, Cloudflare Workers, and Vercel Edge — no rewrites, no adapters to learn from scratch.
- One library, every runtime — write your rate limiting logic once, deploy it anywhere
- Drop-in framework middleware — first-class adapters for Express, Fastify, Hono, and Next.js
- IETF-compliant headers — draft-6 and draft-7 rate limit headers plus
Retry-Afterout of the box - ~3 KB min+gzip — zero dependencies, tree-shakeable ESM
Install
bash
npm install universal-rate-limitQuick Start
ts
import { rateLimit } from 'universal-rate-limit';
const limiter = rateLimit({
windowMs: 60_000, // 1 minute
limit: 60 // 60 requests per window
});
// Use with any Web Standard Request
const result = await limiter(request);
if (result.limited) {
return new Response('Too Many Requests', {
status: 429,
headers: result.headers
});
}Options
All options are optional. Defaults are shown below:
ts
rateLimit({
windowMs: 60_000, // Time window in milliseconds (default: 1 minute)
limit: 60, // Max requests per window (number or async function)
algorithm: 'fixed-window', // 'fixed-window' or 'sliding-window'
headers: 'draft-7', // 'draft-7' or 'draft-6'
store: new MemoryStore(), // Custom store implementation
keyGenerator: req => ip, // Extract client identifier from request
skip: req => false, // Skip rate limiting for certain requests
handler: undefined, // Custom 429 response handler
message: 'Too Many Requests', // Response body (string, object, or function)
statusCode: 429, // HTTP status code when limited
passOnStoreError: false // Fail open if store errors
});Store Interface
Implement the Store interface to use any backend:
ts
import type { Store, IncrementResult } from 'universal-rate-limit';
class MyStore implements Store {
async increment(key: string): Promise<IncrementResult> {
// Increment counter and return { totalHits, resetTime }
}
async decrement(key: string): Promise<void> {
/* ... */
}
async resetKey(key: string): Promise<void> {
/* ... */
}
async resetAll(): Promise<void> {
/* ... */
}
}
const limiter = rateLimit({ store: new MyStore() });A ready-made Redis store is available via @universal-rate-limit/redis.
Examples
Example apps with integration tests are available for each framework:
Framework Middleware
Drop-in adapters are available as separate packages:
| Package | Framework | Install |
|---|---|---|
@universal-rate-limit/express | Express | npm i @universal-rate-limit/express |
@universal-rate-limit/fastify | Fastify | npm i @universal-rate-limit/fastify |
@universal-rate-limit/hono | Hono | npm i @universal-rate-limit/hono |
@universal-rate-limit/nextjs | Next.js App Router | npm i @universal-rate-limit/nextjs |
Runtime Compatibility
| Runtime | Version | Status |
|---|---|---|
| Node.js | >= 20 | Tested |
| Bun | >= 1.0 | Tested |
| Deno | >= 2.0 | Tested |
| Cloudflare Workers | - | Compatible |
| Vercel Edge | - | Compatible |
Documentation
License
MIT