DEV Community

Lucas M Dev
Lucas M Dev

Posted on

How to Implement API Rate Limiting in Node.js (3 Methods Compared)

Rate limiting is one of those things every API needs but most tutorials overcomplicate. Here are 3 practical approaches, from simplest to most robust.

Why Rate Limiting Matters

Without it, a single user (or bot) can:

  • Crash your server with too many requests
  • Scrape your entire database
  • Run up your cloud bill overnight

Method 1: express-rate-limit (Quick & Easy)

npm install express-rate-limit
Enter fullscreen mode Exit fullscreen mode
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per window
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many requests, please try again later.' }
});

app.use('/api/', limiter);
Enter fullscreen mode Exit fullscreen mode

Pros: 5 lines of code, works immediately
Cons: In-memory only — resets on server restart, doesn't work across multiple instances

Method 2: Redis-based Rate Limiting (Production Ready)

npm install rate-limit-redis ioredis
Enter fullscreen mode Exit fullscreen mode
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');

const redis = new Redis(process.env.REDIS_URL);

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  store: new RedisStore({
    sendCommand: (...args) => redis.call(...args),
  }),
});
Enter fullscreen mode Exit fullscreen mode

Pros: Persists across restarts, works with multiple server instances
Cons: Requires Redis

Method 3: Sliding Window with Custom Logic

For more control, build your own:

const requestCounts = new Map();

function slidingWindowLimiter(maxRequests, windowMs) {
  return (req, res, next) => {
    const key = req.ip;
    const now = Date.now();

    if (!requestCounts.has(key)) {
      requestCounts.set(key, []);
    }

    const timestamps = requestCounts.get(key)
      .filter(t => t > now - windowMs);

    if (timestamps.length >= maxRequests) {
      return res.status(429).json({
        error: 'Rate limit exceeded',
        retryAfter: Math.ceil((timestamps[0] + windowMs - now) / 1000)
      });
    }

    timestamps.push(now);
    requestCounts.set(key, timestamps);
    next();
  };
}

// 50 requests per minute
app.use('/api/', slidingWindowLimiter(50, 60 * 1000));
Enter fullscreen mode Exit fullscreen mode

Which Method Should You Use?

Scenario Recommended Method
Side project / MVP Method 1 (express-rate-limit)
Production API Method 2 (Redis)
Custom billing/quotas Method 3 (Sliding window)

Bonus: Per-User Rate Limiting

Instead of limiting by IP, limit by API key:

const limiter = rateLimit({
  windowMs: 60 * 1000,
  max: 30,
  keyGenerator: (req) => req.headers['x-api-key'] || req.ip,
});
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. Always add rate limiting — even a basic one is better than none
  2. Return proper 429 status codes and Retry-After headers
  3. Use Redis in production for consistency across instances
  4. Consider different limits for different endpoints (auth vs data)

More developer guides at lucasmdevdev.github.io

Top comments (0)