Production-grade distributed rate limiting for Node.js. Atomic Lua scripts, Redis Cluster-safe, three battle-tested algorithms. Express and Next.js adapters included.
All three run as atomic Lua scripts on Redis — one round-trip, no race conditions. Switch algorithms with a single option change.
Approximates a true sliding window by weighting the previous window's count. Error bounded at ~0.1% at the window boundary.
When to use
High throughput APIs, predictable billing, rate card enforcement.
const r = await limiter.check({
key: 'user:42',
limit: 1000,
windowMs: 60_000,
})Stores every request timestamp in a sorted set. Exact count of requests in the last N milliseconds — no approximation.
When to use
Billing systems, compliance, low-volume APIs where precision is required.
const r = await limiter.check({
key: 'api:tenant:9',
limit: 100,
windowMs: 3_600_000, // 1h
// algorithm: 'sliding-window-log'
})Tokens refill at a fixed rate. Allows short bursts up to the bucket capacity while smoothing long-term throughput.
When to use
Webhooks, streaming APIs, any use-case where occasional bursts are acceptable.
const r = await limiter.check({
key: 'webhook:sender',
limit: 50, // max burst
windowMs: 60_000, // refill period
// algorithm: 'token-bucket'
})Every detail you'd need to ship reliable rate limiting — from cluster safety to real-time observability.
All state mutations happen in a single EVALSHA call — one Redis round-trip, zero race conditions between INCR and EXPIRE.
Hash-tagged keys keep both window slots on the same slot. {user:42}:sw:... — the cluster router never splits a Lua script across nodes.
When Redis restarts and flushes the script cache, FloodGate catches NOSCRIPT, re-loads via SCRIPT LOAD, and retries — completely transparent to callers.
Strict mode throughout. Typed EventEmitter with discriminated unions for check, blocked, redis:error, and redis:fallback events.
Drop-in middleware for Express with RateLimit-* headers. App Router handler wrapper and Next.js middleware.ts helper for edge runtime.
Next.js 15 SSE-powered observability dashboard. Per-key request counts, block rates, a 60-second sparkline, and live traffic simulation.
import { createLimiter } from 'floodgate-rl' // In-memory — zero infra, perfect for single-process or tests const limiter = createLimiter({ backend: 'memory' }) const result = await limiter.check({ key: 'user:42', limit: 100, windowMs: 60_000, // 1 minute }) // { allowed: true, remaining: 99, resetAt: 1700000060000 } console.log(result.allowed, result.remaining)
Every check is a single atomic operation. No multi-step transactions, no lost updates.
Edge case handling