API Rate Limiting Implementation

Protect your API from abuse. Here's how to implement rate limiting in Node.js.

Why Rate Limit

Without limits:

  • One client can overwhelm your server
  • Brute force attacks succeed
  • Costs spiral (if using paid services)

With limits:

  • Fair resource distribution
  • Protection from abuse
  • Predictable performance

Simple Token Bucket

class RateLimiter {
  constructor(maxRequests, windowMs) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
    this.clients = new Map();
  }
  
  isAllowed(clientId) {
    const now = Date.now();
    const client = this.clients.get(clientId);
    
    if (!client || now - client.windowStart > this.windowMs) {
      this.clients.set(clientId, { count: 1, windowStart: now });
      return true;
    }
    
    if (client.count < this.maxRequests) {
      client.count++;
      return true;
    }
    
    return false;
  }
}

// 100 requests per minute
const limiter = new RateLimiter(100, 60000);

Express Middleware

Using `express-rate-limit`:

npm install express-rate-limit
const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per window
  message: {
    error: 'Too many requests, please try again later'
  },
  standardHeaders: true, // Return rate limit info in headers
  legacyHeaders: false
});

// Apply to all API routes
app.use('/api', apiLimiter);

Per-Endpoint Limits

Different endpoints have different costs:

const strictLimiter = rateLimit({
  windowMs: 60000,
  max: 5 // Only 5 per minute
});

const relaxedLimiter = rateLimit({
  windowMs: 60000,
  max: 100
});

app.post('/api/login', strictLimiter, loginHandler);
app.get('/api/products', relaxedLimiter, productsHandler);

Redis for Distributed Systems

In-memory limiters don't work across multiple servers. Use Redis:

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

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

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

Response Headers

Good APIs tell clients their limit status:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1639641600
Retry-After: 60

Clients can back off gracefully.

Identifying Clients

Options:

  • IP address (default, can be shared)
  • API key (best for APIs)
  • User ID (after authentication)
const limiter = rateLimit({
  keyGenerator: (req) => {
    return req.user?.id || req.ip;
  },
  // ...
});

What Limits to Set

| Endpoint Type | Suggestion | |--------------|------------| | Login | 5-10 per minute | | Password reset | 3-5 per hour | | General API | 60-100 per minute | | Expensive operations | 10-20 per minute | | Public endpoints | Higher, but still limited |

Start conservative. Adjust based on real usage.

Rate limiting is basic hygiene. Don't skip it.

Back to Journal