Skip to content

universal-rate-limit

npm versionnpm downloadstypeslicensebundle size

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-After out of the box
  • ~3 KB min+gzip — zero dependencies, tree-shakeable ESM

Install

bash
npm install universal-rate-limit

Quick 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:

PackageFrameworkInstall
@universal-rate-limit/expressExpressnpm i @universal-rate-limit/express
@universal-rate-limit/fastifyFastifynpm i @universal-rate-limit/fastify
@universal-rate-limit/honoHononpm i @universal-rate-limit/hono
@universal-rate-limit/nextjsNext.js App Routernpm i @universal-rate-limit/nextjs

Runtime Compatibility

RuntimeVersionStatus
Node.js>= 20Tested
Bun>= 1.0Tested
Deno>= 2.0Tested
Cloudflare Workers-Compatible
Vercel Edge-Compatible

Documentation

View the full documentation

License

MIT

Released under the MIT License.