Home/Docs/rate-limiter
RL

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#

TBToken Bucket

Tokens refill at a fixed rate up to bucketSize. Allows controlled bursts. Best for APIs with bursty clients.

FWFixed Window

Hard cap per fixed time window. Simplest, lowest memory. Susceptible to boundary bursts.

SWLSliding Window Log

Stores a timestamp per request. Exact enforcement, no boundary burst. Higher memory (O(maxRequests)).

SWCSliding Window Counter

Two counters + weighted interpolation. ~Exact accuracy, O(1) memory. Recommended default.

Configuration#

PropTypeDescription
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.
keyPrefixstringPrefix added to every store key.
bucketSizenumberToken bucket only — maximum capacity (max burst).
tokensPerSecondnumberToken bucket only — steady-state refill rate.
windowMsnumberWindow algorithms — duration of the rate limit window in ms.
maxRequestsnumberWindow algorithms — max allowed requests per window.
circuitBreakerRateLimiterCircuitBreakerConfigRedis 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:

main.ts
const app = await NestFactory.create(AppModule);
app.set('trust proxy', 1); // trust one proxy hop

Examples#

From basic to production-grade — copy and adapt.

api.middleware.ts
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-limiterexpress-rate-limitrate-limiter-flexible
Algorithms4 (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.