This document covers advanced usage patterns and integration strategies for express-rate-limit.
The rate limiter returns an enhanced middleware with additional methods:
interface RateLimitRequestHandler extends RequestHandler {
// Reset hit counter for specific client key
resetKey(key: string): void;
// Get current hit count and reset time for specific client key
getKey(key: string): Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
}
interface ClientRateLimitInfo {
totalHits: number;
resetTime: Date | undefined;
}import rateLimit from "express-rate-limit";
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100
});
// Reset a specific user's rate limit
app.post("/admin/reset-rate-limit", async (req, res) => {
const { userId } = req.body;
await limiter.resetKey(userId);
res.json({ message: "Rate limit reset successfully" });
});
// Check rate limit status
app.get("/api/rate-limit-status", async (req, res) => {
const info = await limiter.getKey(req.ip);
res.json({
hits: info?.totalHits || 0,
resetTime: info?.resetTime
});
});Rate limit information is attached to each request:
interface RateLimitInfo {
limit: number; // Maximum requests allowed
used: number; // Current request count
remaining: number; // Remaining requests in window
resetTime: Date | undefined; // When the window resets
key: string; // The key used for this client
}app.use(rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100,
requestPropertyName: "rateLimit" // Default property name
}));
app.use((req, res, next) => {
const rateLimitInfo = req.rateLimit;
// Add custom headers
res.set("X-Requests-Remaining", rateLimitInfo.remaining.toString());
res.set("X-Requests-Used", rateLimitInfo.used.toString());
// Log high usage
if (rateLimitInfo.remaining < 10) {
console.warn(`Client ${rateLimitInfo.key} approaching rate limit`);
}
next();
});import { ipKeyGenerator } from "express-rate-limit";
const limiter = rateLimit({
keyGenerator: (req, res) => {
return ipKeyGenerator(req.ip); // Uses default IPv6 subnet of 56
}
});
// Custom IPv6 subnet
const strictLimiter = rateLimit({
keyGenerator: (req, res) => {
return ipKeyGenerator(req.ip, 64); // More granular IPv6 grouping
}
});
// Disable IPv6 subnet grouping
const exactIpLimiter = rateLimit({
keyGenerator: (req, res) => {
return ipKeyGenerator(req.ip, false); // Use exact IPv6 addresses
}
});// User-based rate limiting
const userLimiter = rateLimit({
keyGenerator: (req, res) => {
return req.user?.id || req.ip; // Use user ID if authenticated, IP otherwise
}
});
// API key-based rate limiting
const apiLimiter = rateLimit({
keyGenerator: (req, res) => {
const apiKey = req.headers["x-api-key"];
return apiKey || req.ip;
}
});
// Route-specific rate limiting
const routeLimiter = rateLimit({
keyGenerator: (req, res) => {
return `${req.ip}:${req.route.path}`; // Different limits per route
}
});
// Composite key generation
const compositeLimiter = rateLimit({
keyGenerator: (req, res) => {
const userId = req.user?.id;
const endpoint = req.path;
return userId ? `user:${userId}:${endpoint}` : `ip:${req.ip}:${endpoint}`;
}
});type RateLimitExceededEventHandler = (
request: Request,
response: Response,
next: NextFunction,
optionsUsed: Options,
) => void;const customLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100,
handler: (req, res, next, options) => {
const rateLimitInfo = req.rateLimit;
// Custom response with detailed information
res.status(429).json({
error: "Rate limit exceeded",
message: "Too many requests from this client",
limit: rateLimitInfo.limit,
used: rateLimitInfo.used,
remaining: rateLimitInfo.remaining,
resetTime: rateLimitInfo.resetTime,
retryAfter: Math.ceil((rateLimitInfo.resetTime?.getTime() - Date.now()) / 1000)
});
// Log rate limit exceeded events
console.warn(`Rate limit exceeded for ${rateLimitInfo.key}`, {
ip: req.ip,
userAgent: req.get("User-Agent"),
path: req.path,
timestamp: new Date().toISOString()
});
}
});const conditionalLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100,
handler: (req, res, next, options) => {
const rateLimitInfo = req.rateLimit;
// Different responses based on client type
if (req.headers["x-api-key"]) {
// API client response
res.status(429).json({
error: "RATE_LIMIT_EXCEEDED",
code: 1001,
retryAfter: Math.ceil((rateLimitInfo.resetTime?.getTime() - Date.now()) / 1000)
});
} else {
// Web client response
res.status(429).render("rate-limit-exceeded", {
resetTime: rateLimitInfo.resetTime,
limit: rateLimitInfo.limit
});
}
}
});const authenticatedLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100,
skip: (req, res) => {
// Skip rate limiting for authenticated premium users
return req.user?.plan === "premium";
}
});const selectiveLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100,
skip: (req, res) => {
// Skip rate limiting for health checks and monitoring
return req.path === "/health" ||
req.path === "/metrics" ||
req.headers["user-agent"]?.includes("monitoring");
}
});const smartLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100,
skipSuccessfulRequests: true, // Only count failed requests
requestWasSuccessful: (req, res) => {
// Custom success logic
return res.statusCode >= 200 && res.statusCode < 400;
}
});
// Authentication-specific filtering
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 5,
skipSuccessfulRequests: true, // Only count failed login attempts
requestWasSuccessful: (req, res) => {
// Success means authentication succeeded
return res.statusCode === 200 && res.locals.authSuccess;
}
});// Global rate limiter
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 1000,
message: "Too many requests from this IP"
});
// API-specific rate limiter
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100,
keyGenerator: (req, res) => `api:${req.ip}`,
message: "API rate limit exceeded"
});
// Authentication rate limiter
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 5,
keyGenerator: (req, res) => `auth:${req.ip}`,
skipSuccessfulRequests: true
});
// Apply multiple limiters
app.use(globalLimiter);
app.use("/api", apiLimiter);
app.use("/auth", authLimiter);// Warn at 80% of limit, block at 100%
const graduatedLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100,
handler: (req, res, next, options) => {
res.status(429).json({ error: "Rate limit exceeded" });
}
});
// Warning middleware (applied before rate limiter)
const warningMiddleware = (req, res, next) => {
// This would need to check the store directly
const rateLimitInfo = req.rateLimit;
if (rateLimitInfo && rateLimitInfo.remaining <= rateLimitInfo.limit * 0.2) {
res.set("X-Rate-Limit-Warning", "Approaching rate limit");
}
next();
};
app.use(graduatedLimiter);
app.use(warningMiddleware);const dynamicLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: async (req, res) => {
// Adjust limits based on system load
const systemLoad = await getSystemLoad();
if (systemLoad > 0.8) return 50; // Reduced limit under high load
if (systemLoad < 0.3) return 200; // Increased limit under low load
return 100; // Normal limit
},
message: async (req, res) => {
const systemLoad = await getSystemLoad();
return systemLoad > 0.8
? "System under high load, please retry later"
: "Rate limit exceeded";
}
});// Using Redis for distributed rate limiting
const distributedLimiter = rateLimit({
store: new RedisStore(redisClient),
windowMs: 15 * 60 * 1000,
limit: 100,
keyGenerator: (req, res) => {
// Global key across all instances
return `global:${req.ip}`;
}
});
// Per-service rate limiting
const serviceLimiter = rateLimit({
store: new RedisStore(redisClient),
windowMs: 15 * 60 * 1000,
limit: 1000,
keyGenerator: (req, res) => {
// Service-specific key
return `service:${process.env.SERVICE_NAME}:${req.ip}`;
}
});const resilientLimiter = rateLimit({
store: externalStore,
windowMs: 15 * 60 * 1000,
limit: 100,
passOnStoreError: true, // Continue serving requests if store fails
});
// Add error monitoring
app.use((error, req, res, next) => {
if (error.message.includes("rate limit store")) {
console.error("Rate limit store error:", error);
// Send alert to monitoring system
// Fall back to allowing the request
next();
} else {
next(error);
}
});const monitoredLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100,
handler: (req, res, next, options) => {
// Monitor rate limit violations
const rateLimitInfo = req.rateLimit;
// Send metrics to monitoring system
metrics.increment("rate_limit.exceeded", {
key: rateLimitInfo.key,
path: req.path,
method: req.method
});
// Log detailed information
logger.warn("Rate limit exceeded", {
key: rateLimitInfo.key,
ip: req.ip,
path: req.path,
userAgent: req.get("User-Agent"),
used: rateLimitInfo.used,
limit: rateLimitInfo.limit
});
res.status(429).json({ error: "Rate limit exceeded" });
}
});// Health check endpoint that reports rate limiter status
app.get("/health", async (req, res) => {
try {
// Test store connectivity
await limiter.getKey("health-check");
res.json({
status: "healthy",
rateLimiter: {
store: "connected",
type: limiter.store.constructor.name
}
});
} catch (error) {
res.status(503).json({
status: "unhealthy",
rateLimiter: {
store: "disconnected",
error: error.message
}
});
}
});