All posts
SecurityApr 26, 2026·6 min read

API Rate Limiting Strategies for Node.js

Fixed window, sliding window, token bucket, and leaky bucket — the four rate limiting algorithms, when to use each, and how to implement distributed rate limiting across Node.js replicas.

Why rate limiting matters

Without rate limiting, a single client (malicious or buggy) can exhaust your server's capacity and deny service to legitimate users. Rate limiting protects against brute force attacks, API abuse, runaway clients, and DoS attempts.

The four algorithms

Fixed window — Count requests in a fixed time window (e.g., per minute). Simple and cheap. Weakness: a burst at the window boundary allows 2x the rate limit in a short period.

Sliding window log — Track the timestamp of each request. Count entries in the last N seconds. Most accurate. Memory-intensive at high request rates.

Sliding window counter — Approximate sliding window using two fixed windows with interpolation. Accurate to within 0.4% for most traffic patterns. The best production tradeoff.

Token bucket — A bucket fills with tokens at a steady rate; each request consumes one. Allows controlled bursts. Best when you want to allow occasional bursts without penalizing bursty-but-average-compliant clients.

Distributed rate limiting with Redis

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from 'ioredis';

// Sliding window — most accurate
const limiter = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(100, '1 m'), // 100 req/min
  analytics: true,
});

app.use(async (req, res, next) => {
  const identifier = req.user?.id ?? req.ip;
  const { success, limit, remaining, reset } = await limiter.limit(identifier);

  res.setHeader('X-RateLimit-Limit', limit);
  res.setHeader('X-RateLimit-Remaining', remaining);
  res.setHeader('X-RateLimit-Reset', new Date(reset).toISOString());

  if (!success) {
    return res.status(429).json({
      error: { code: 'RATE_LIMITED', message: 'Too many requests. Please slow down.' },
    });
  }
  next();
});

Tiered rate limits

const limits = {
  free: Ratelimit.slidingWindow(60, '1 m'),
  pro: Ratelimit.slidingWindow(600, '1 m'),
  enterprise: Ratelimit.slidingWindow(6000, '1 m'),
};

app.use(async (req, res, next) => {
  const tier = req.user?.plan ?? 'free';
  const limiter = new Ratelimit({ redis, limiter: limits[tier] });
  const { success } = await limiter.limit(req.user?.id ?? req.ip);
  if (!success) return res.status(429).json({ error: 'Rate limit exceeded' });
  next();
});

Endpoint-specific limits

Apply stricter limits on sensitive endpoints: auth, password reset, OTP generation. These are brute-force targets and need tighter constraints than general API endpoints.

// Auth endpoints: 5 per 15 minutes per IP
const authLimiter = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(5, '15 m'),
});

app.post('/auth/login', rateLimit(authLimiter, (req) => req.ip));
app.post('/auth/reset-password', rateLimit(authLimiter, (req) => req.ip));

Ready to put this into practice?

Deploy your Node.js app to production in minutes — zero YAML, automatic CI/CD, and HTTPS included.