3.8 KiB
rate-limit-middleware
(no submodules)
Purpose
Provides framework-agnostic middleware factory helpers that wrap createRateLimiter for easy integration into HTTP servers, API gateways, and custom request pipelines. All middleware functions accept req: unknown and return a Promise<RateLimitResult>, keeping them decoupled from any specific framework (Hono, Express, Fastify, Bun's native server, etc.).
The three exported helpers cover the most common patterns: a simple checker function, IP-based limiting, and route-specific per-path limits. For Hono-specific wiring, consumers can use a thin adapter that reads result.allowed and returns a 429 response.
Features
createRateLimitChecker— returns a(context) => Promise<RateLimitResult>wrappergetRateLimitResult— low-level direct storage+algorithm callcreateIpRateLimiter— IP-key extraction with configurablekeyExtractor,skip,onLimitedcreateRouteRateLimiter— per-route config map with route-prefix matching- All factories accept an optional
skippredicate to bypass limiting for health probes onLimitedcallback for telemetry, logging, or 429-response generation
Usage: Basic Example
import { createRateLimitChecker } from "@suaveplan/rate-limiter";
const check = createRateLimitChecker({
defaultConfig: { limit: 100, window: 60_000 },
});
const result = await check({ key: "user:42" });
if (!result.allowed) {
return new Response("Too Many Requests", { status: 429 });
}
Usage: Advanced Example
import { createIpRateLimiter, createRouteRateLimiter, MemoryStorage } from "@suaveplan/rate-limiter";
const storage = new MemoryStorage({ maxSize: 10_000 });
// IP-based limiter that skips health checks
const ipLimiter = createIpRateLimiter(
{ limit: 200, window: 60_000 },
storage,
{
keyExtractor: (req) => (req as Request).headers.get("x-forwarded-for") ?? "unknown",
skip: (req) => (req as Request).url.endsWith("/health"),
onLimited: (result) => console.warn("IP limited", result),
}
);
// Route-specific limits
const routeLimiter = createRouteRateLimiter(
{
"/api/auth/login": { limit: 5, window: 60_000 },
"/api/search": { limit: 50, window: 10_000 },
},
storage
);
const result = await ipLimiter(request);
if (!result.allowed) {
return new Response("Too Many Requests", { status: 429 });
}
API Reference
createRateLimitChecker(options)
(options: RateLimiterOptions) => (context: RateLimitContext) => Promise<RateLimitResult>
getRateLimitResult(context, storage, config)
(context: RateLimitContext, storage: RateLimitStorage, config: RateLimitConfig) => Promise<RateLimitResult>
Low-level helper — creates a fresh SlidingWindow per call, restoring persisted state from storage.
createIpRateLimiter(config, storage?, middlewareOptions?)
Returns (req: unknown) => Promise<RateLimitResult>.
createRouteRateLimiter(routes, storage?, middlewareOptions?)
Returns (req: unknown) => Promise<RateLimitResult>. Matches route prefixes in insertion order.
RateLimiterMiddlewareOptions
| Field | Type | Description |
|---|---|---|
keyExtractor? |
(req: unknown) => string |
Extract key from request |
onLimited? |
(result: RateLimitResult) => void |
Called on every check (use to send 429) |
skip? |
(req: unknown) => boolean |
Skip limiting for matching requests |
Implementation Notes
createIpRateLimiter and createRouteRateLimiter each construct one createRateLimiter instance per route/IP config at factory time, sharing a single storage adapter. Route matching in createRouteRateLimiter uses key.startsWith(route) || key.includes(route) — for production use with complex routing rules, consumers should provide a custom keyExtractor that returns the matched route prefix.