Guides
Express.js Integration
Protect your Express routes with Limitly. Learn how to create middleware, handle errors, and set proper HTTP headers.
Express.js Integration
Basic Middleware
// middleware/rate-limit.ts
import { createClient } from 'limitly-sdk';
const client = createClient({ serviceId: 'express-api' });
export async function rateLimitMiddleware(req, res, next) {
const identifier = req.user?.id || req.ip || 'anonymous';
const result = await client.checkRateLimit(identifier);
if (!result.allowed) {
return res.status(429).json({ error: 'Rate limit exceeded' });
}
next();
}Usage
// app.ts
import { rateLimitMiddleware } from './middleware/rate-limit';
app.use('/api', rateLimitMiddleware);
// or
app.get('/api/data', rateLimitMiddleware, (req, res) => {
res.json({ data: 'Protected data' });
});Per-Route Limits
function createRateLimitMiddleware(capacity: number, refillRate: number) {
const client = createClient({ serviceId: 'express-api' });
return async (req, res, next) => {
const result = await client.checkRateLimit({ identifier: req.user?.id || req.ip, capacity, refillRate });
if (!result.allowed) return res.status(429).json({ error: 'Rate limit exceeded' });
next();
};
}
export const strictRateLimit = createRateLimitMiddleware(10, 1);
export const moderateRateLimit = createRateLimitMiddleware(100, 10);
// Usage
app.post('/api/login', strictRateLimit, handler);
app.get('/api/data', moderateRateLimit, handler);User-Based Rate Limiting
Rate limit based on authenticated users:
// middleware/user-rate-limit.ts
import { createClient } from 'limitly-sdk';
import type { Request, Response, NextFunction } from 'express';
const client = createClient({ serviceId: 'user-api' });
interface AuthenticatedRequest extends Request {
user?: {
id: string;
plan: 'free' | 'pro' | 'enterprise';
};
}
export async function userRateLimitMiddleware(
req: AuthenticatedRequest,
res: Response,
next: NextFunction
) {
// Require authentication
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Different limits based on user plan
const limits = {
free: { capacity: 100, refillRate: 10 },
pro: { capacity: 1000, refillRate: 100 },
enterprise: { capacity: 10000, refillRate: 1000 }
};
const userLimits = limits[req.user.plan] || limits.free;
const result = await client.checkRateLimit({
identifier: req.user.id,
...userLimits
});
// Set headers
if (result.limit) res.setHeader('X-RateLimit-Limit', result.limit.toString());
if (result.remaining !== undefined) {
res.setHeader('X-RateLimit-Remaining', result.remaining.toString());
}
if (!result.allowed) {
const retryAfter = result.reset
? Math.ceil((result.reset - Date.now()) / 1000)
: 60;
res.setHeader('Retry-After', retryAfter.toString());
return res.status(429).json({
error: 'Rate limit exceeded',
message: `You have exceeded your ${req.user.plan} plan limits`,
retryAfter
});
}
next();
}Error Handling
Handle rate limit errors gracefully:
// middleware/rate-limit.ts
import { createClient } from 'limitly-sdk';
import type { Request, Response, NextFunction } from 'express';
const client = createClient({ serviceId: 'express-api' });
export async function rateLimitMiddleware(
req: Request,
res: Response,
next: NextFunction
) {
try {
const identifier = (req as any).user?.id || req.ip || 'anonymous';
const result = await client.checkRateLimit(identifier);
// Set headers
if (result.limit) res.setHeader('X-RateLimit-Limit', result.limit.toString());
if (result.remaining !== undefined) {
res.setHeader('X-RateLimit-Remaining', result.remaining.toString());
}
if (result.reset) {
res.setHeader('X-RateLimit-Reset', Math.ceil(result.reset / 1000).toString());
}
if (!result.allowed) {
const retryAfter = result.reset
? Math.ceil((result.reset - Date.now()) / 1000)
: 60;
res.setHeader('Retry-After', retryAfter.toString());
return res.status(429).json({
error: 'Rate limit exceeded',
message: 'Too many requests, please try again later',
retryAfter,
resetAt: result.reset ? new Date(result.reset).toISOString() : undefined
});
}
next();
} catch (error) {
// Handle Redis connection errors, timeouts, etc.
console.error('Rate limit check failed:', error);
// Fail open - allow request if rate limiting fails
// In production, you might want to log this and alert
next();
}
}Router-Level Application
Apply rate limiting to Express routers:
// routes/api.ts
import { Router } from 'express';
import { rateLimitMiddleware } from '../middleware/rate-limit';
const router = Router();
// Apply to all routes in this router
router.use(rateLimitMiddleware);
router.get('/data', (req, res) => {
res.json({ data: 'Protected data' });
});
router.post('/submit', (req, res) => {
res.json({ success: true });
});
export default router;Conditional Limiting
export async function conditionalRateLimitMiddleware(req, res, next) {
if (req.user?.isAdmin) return next();
if (req.ip?.startsWith('192.168.')) return next();
const result = await checkLimit(req.user?.id || req.ip);
if (!result.allowed) return res.status(429).json({ error: 'Rate limit exceeded' });
next();
}Best Practices
- Use service IDs for isolation
- Handle errors gracefully (fail open)
- Use authenticated user IDs when available
- Apply different limits to different routes