rate-limiter
v0.1.0@backendkit-labs/rate-limiter
Four algorithms, one interface. Redis-ready with atomic Lua scripts.
Overview#
Rate limiting protects your services from abusive traffic, runaway clients, and accidental self-denial-of-service. @backendkit-labs/rate-limiter provides four algorithms behind a single consume(key) call, so you can start in-memory with zero dependencies and migrate to Redis without changing application code.
Every call returns Result<RateLimitResult, RateLimitError> — a store failure (Redis down) is distinct from a rate-limited request (allowed: false). You decide whether to fail open or return 503 on store errors; a 429 is always an explicit branch.
Algorithms#
Tokens refill at a fixed rate up to bucketSize. Allows controlled bursts. Best for APIs with bursty clients.
Hard cap per fixed time window. Simplest, lowest memory. Susceptible to boundary bursts.
Stores a timestamp per request. Exact enforcement, no boundary burst. Higher memory (O(maxRequests)).
Two counters + weighted interpolation. ~Exact accuracy, O(1) memory. Recommended default.
Configuration#
| Prop | Type | Description |
|---|---|---|
| algorithm | 'token-bucket' | 'fixed-window' | 'sliding-window-log' | 'sliding-window-counter' | Algorithm to use. |
| store | 'memory' | 'redis' | Store backend. Pass a pre-configured ioredis instance for custom setups. |
| keyPrefix | string | Prefix added to every store key. |
| bucketSize | number | Token bucket only — maximum capacity (max burst). |
| tokensPerSecond | number | Token bucket only — steady-state refill rate. |
| windowMs | number | Window algorithms — duration of the rate limit window in ms. |
| maxRequests | number | Window algorithms — max allowed requests per window. |
| circuitBreaker | RateLimiterCircuitBreakerConfig | Redis only — opens on Redis failures and optionally falls back to MemoryStore. |
Redis Store#
All algorithm logic runs as atomic Lua scripts via EVALSHA (with EVAL fallback on NOSCRIPT). Consume + check + update is a single round-trip — no race conditions across multiple instances.
Add circuitBreaker: { fallbackToMemory: true } to keep limits enforced locally when Redis is unavailable. Each instance maintains its own counter during the outage, so the effective limit becomes maxRequests × instanceCount — an acceptable trade-off for continued availability.
NestJS Integration#
Register RateLimiterModule.forRoot() once with globalGuard: true to protect every route. Override per-route with @RateLimit(config) or disable entirely with @RateLimit(null).
Use forRootAsync when the configuration depends on ConfigService or other injectable providers. The module re-creates the limiter instance on each initialization — safe for hot-reload in development.
Trust proxy for IP-based limiting
When running behind a reverse proxy (Nginx, Cloudflare, AWS ALB), configure Express trust proxy so request.ip reflects the real client IP from X-Forwarded-For:
const app = await NestFactory.create(AppModule); app.set('trust proxy', 1); // trust one proxy hop
Examples#
From basic to production-grade — copy and adapt.
import { RateLimiterFactory, type TokenBucketConfig } from '@backendkit-labs/rate-limiter'; const config: TokenBucketConfig = { algorithm: 'token-bucket', store: 'memory', bucketSize: 20, tokensPerSecond: 5, keyPrefix: 'api:', }; const limiter = RateLimiterFactory.create(config); app.use(async (req, res, next) => { const result = await limiter.consume(req.ip ?? 'unknown'); if (!result.ok) return next(); // store error — fail open res.set('X-RateLimit-Limit', String(result.value.totalLimit)); res.set('X-RateLimit-Remaining', String(result.value.remaining)); res.set('X-RateLimit-Reset', String(Math.ceil(result.value.resetAt / 1000))); if (!result.value.allowed) { const retryAfter = Math.ceil((result.value.resetAt - Date.now()) / 1000); return res.status(429).set('Retry-After', String(retryAfter)) .json({ error: 'too_many_requests', retryAfter }); } next(); });
⚖️ vs. Alternatives#
Comparing against express-rate-limit and node-rate-limiter-flexible — the two most common rate limiting libraries in the Node.js ecosystem.
| Feature | @backendkit-labs/rate-limiter | express-rate-limit | rate-limiter-flexible |
|---|---|---|---|
| Algorithms | 4 (TB, FW, SWL, SWC) | 1 (fixed window) | ✅ Multiple |
| In-memory store | ✅ Built-in | ✅ Default | ✅ Built-in |
| Redis support | ✅ Atomic Lua scripts | ⚠️ Via plugin | ✅ Built-in |
| Atomic multi-instance | ✅ EVALSHA | ⚠️ Depends on store | ✅ |
| Circuit breaker | ✅ Integrated | ❌ | ❌ |
| Result<T,E> interface | ✅ No throws | ❌ Throws / middleware | ❌ Throws |
| Multi-weight requests | ✅ consume(key, weight) | ❌ | ✅ |
| NestJS integration | ✅ Module + @RateLimit() | ⚠️ Manual adapter | ⚠️ Manual adapter |
| Zero deps (core) | ✅ | ✅ | ✅ |
✅ Supported · ❌ Not supported · ⚠️ Partial / workaround needed. Download counts are approximate weekly npm averages.