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
| Option | Value | Description |
|---|
windowMs | 15 * 60 * 1000 | 15-minute time window |
max | 100 | Maximum 100 requests per window |
message | Custom string | Error message returned when limit exceeded |
The rate limiter is disabled in development mode to facilitate testing and debugging.
How It Works
- Request Tracking: Each incoming request is tracked by IP address
- Counter Increment: Request counter increases for each request from the same IP
- Limit Check: If counter exceeds
max within windowMs, requests are rejected
- Window Reset: Counter resets after the time window expires
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
| Middleware | Purpose |
|---|
| helmet | Sets secure HTTP headers |
| hpp | Prevents HTTP Parameter Pollution |
| morgan | Logs HTTP requests |
| express-rate-limit | Rate limiting and DDoS protection |
| cors | Cross-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
- Check
NODE_ENV is not set to development
- Verify middleware is applied before routes
- Check if proxy configuration is correct
Too Many False Positives
- Increase
max value
- Increase
windowMs duration
- Use
skipSuccessfulRequests: true for authentication
- Implement user-based tracking instead of IP-based