Skip to main content

Overview

The Crypto Shop Backend implements rate limiting using express-rate-limit to protect against brute-force attacks, DDoS attempts, and API abuse. The rate limiter is configured globally and can be customized per route.

Global Rate Limiting

A global rate limiter is applied to all API endpoints in non-development environments.

Configuration

File: src/api/index.js:26
import rateLimit from "express-rate-limit";

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 100,                   // 100 requests per window
  message: "Too many requests, please try again later"
});

// Apply only in production/staging
if (process.env.NODE_ENV !== "development") {
  app.use(limiter);
}

Configuration Options

OptionValueDescription
windowMs15 * 60 * 100015-minute time window
max100Maximum 100 requests per window
messageCustom stringError message returned when limit exceeded
The rate limiter is disabled in development mode to facilitate testing and debugging.

How It Works

  1. Request Tracking: Each incoming request is tracked by IP address
  2. Counter Increment: Request counter increases for each request from the same IP
  3. Limit Check: If counter exceeds max within windowMs, requests are rejected
  4. Window Reset: Counter resets after the time window expires

Response Headers

The rate limiter adds headers to responses:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1678901234567
  • X-RateLimit-Limit: Maximum requests allowed
  • X-RateLimit-Remaining: Requests remaining in current window
  • X-RateLimit-Reset: Timestamp when the limit resets

Error Response

When the rate limit is exceeded: Status Code: 429 Too Many Requests
{
  "message": "Too many requests, please try again later"
}

Advanced Configuration

Custom Rate Limiters

You can create specific rate limiters for sensitive endpoints:
// Strict limiter for authentication endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,  // Only 5 login attempts per 15 minutes
  message: "Too many login attempts, please try again later",
  skipSuccessfulRequests: true  // Don't count successful logins
});

app.use('/api/auth/login', authLimiter);

IP-Based Tracking

If your application is behind a proxy (like Nginx or a CDN), you must configure express-rate-limit to trust proxy headers to correctly identify client IP addresses.
import rateLimit from "express-rate-limit";

// Trust proxy to get real client IP
app.set('trust proxy', 1);

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,  // Return rate limit info in headers
  legacyHeaders: false,   // Disable X-RateLimit-* headers
});

Store Options

By default, express-rate-limit uses an in-memory store. For production with multiple servers, use a distributed store:
import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";
import Redis from "ioredis";

const redis = new Redis({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT
});

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  store: new RedisStore({
    client: redis,
    prefix: 'rate-limit:'
  })
});

Route-Specific Examples

Authentication Routes

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: "Too many login attempts"
});

app.post('/api/auth/login', loginLimiter, loginHandler);
app.post('/api/auth/register', loginLimiter, registerHandler);

API Endpoints

const apiLimiter = rateLimit({
  windowMs: 60 * 1000,  // 1 minute
  max: 30,              // 30 requests per minute
  message: "API rate limit exceeded"
});

app.use('/api/', apiLimiter);

Public vs Authenticated Users

const publicLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 50,
  skip: (req) => req.user !== undefined  // Skip for authenticated users
});

app.use(publicLimiter);

Monitoring and Logging

Log rate limit violations for security monitoring:
import rateLimit from "express-rate-limit";
import morgan from "morgan";

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  handler: (req, res) => {
    // Log the violation
    console.warn(`Rate limit exceeded for IP: ${req.ip}`);
    
    res.status(429).json({
      error: "Too many requests, please try again later"
    });
  }
});

Security Middleware Stack

The application uses multiple security layers: File: src/api/index.js:22
import helmet from "helmet";
import hpp from "hpp";
import morgan from "morgan";
import rateLimit from "express-rate-limit";

// Security headers
app.use(helmet());

// HTTP Parameter Pollution protection
app.use(hpp());

// Request logging
app.use(morgan(process.env.NODE_ENV === "production" ? "combined" : "dev"));

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: "Too many requests, please try again later"
});

if (process.env.NODE_ENV !== "development") {
  app.use(limiter);
}

Security Middleware Overview

MiddlewarePurpose
helmetSets secure HTTP headers
hppPrevents HTTP Parameter Pollution
morganLogs HTTP requests
express-rate-limitRate limiting and DDoS protection
corsCross-Origin Resource Sharing control

Best Practices

1. Adjust Limits by Endpoint Type

Different endpoints have different security requirements. Authentication endpoints should have stricter limits than read-only API endpoints.
  • Authentication: 5 requests per 15 minutes
  • Write Operations: 20 requests per 15 minutes
  • Read Operations: 100 requests per 15 minutes

2. Environment-Specific Configuration

const getMaxRequests = () => {
  switch (process.env.NODE_ENV) {
    case 'production':
      return 100;
    case 'staging':
      return 200;
    case 'development':
      return 1000;
    default:
      return 100;
  }
};

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: getMaxRequests()
});

3. Whitelist Trusted IPs

Whitelisting should be used carefully. Only whitelist trusted services and internal infrastructure.
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  skip: (req) => {
    const trustedIPs = ['192.168.1.1', '10.0.0.1'];
    return trustedIPs.includes(req.ip);
  }
});

4. Custom Key Generators

Track limits by user ID instead of IP:
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  keyGenerator: (req) => {
    // Use user ID if authenticated, otherwise IP
    return req.user?.id || req.ip;
  }
});

Dependencies

{
  "express-rate-limit": "^7.1.5",
  "helmet": "^7.1.0",
  "hpp": "^0.2.3",
  "morgan": "^1.10.0"
}

Testing Rate Limits

Test your rate limits with a simple script:
for (let i = 0; i < 105; i++) {
  fetch('http://localhost:5000/api/health')
    .then(res => console.log(`Request ${i}: ${res.status}`))
    .catch(err => console.error(err));
}
Expected output:
  • Requests 1-100: Status 200
  • Requests 101-105: Status 429

Troubleshooting

Rate Limit Not Working

  1. Check NODE_ENV is not set to development
  2. Verify middleware is applied before routes
  3. Check if proxy configuration is correct

Too Many False Positives

  1. Increase max value
  2. Increase windowMs duration
  3. Use skipSuccessfulRequests: true for authentication
  4. Implement user-based tracking instead of IP-based

Build docs developers (and LLMs) love