This document covers the complete integration and production-grade architecture for Redis-based caching and rate limiting inside an Express / Node.js backend using TypeScript. Each step is self-contained and production-tested.
Install the Redis client driver, the Express rate-limiting library, and its Redis storage adapter. All three are required for both the rate limiter and the cache middleware.
bun add ioredis express-rate-limit rate-limit-redis
| Package | Purpose |
|---|---|
ioredis |
Full-featured Redis client with TypeScript support, pipelining, and cluster mode |
express-rate-limit |
Middleware for enforcing request rate limits on Express routes |
rate-limit-redis |
Storage adapter that backs express-rate-limit with Redis instead of in-process memory |
File: src/utils/redis.ts
import { Redis } from 'ioredis';
const redisUrl = process.env.REDIS_URL;
if (!redisUrl) {
console.warn(
'REDIS_URL is not defined. Caching and rate limiting will be disabled.'
);
}
export const redis = redisUrl ? new Redis(redisUrl) : null;
Key design decisions:
redis export is typed as Redis | null. All consumers must guard against null before calling Redis commands. This prevents crashes when REDIS_URL is absent in local development or CI environments.ioredis automatically handles reconnection with exponential backoff by default. You do not need to manually implement retry logic.?tls=true to the connection URL or pass { tls: {} } in the options object when constructing new Redis().File: src/middleware/rateLimiter.ts
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import { redis } from '@/utils/redis';
interface LimiterOptions {
windowMs: number;
limit: number;
message: string;
}
export const createRateLimiter = (options: LimiterOptions) => {
return rateLimit({
windowMs: options.windowMs,
limit: options.limit,
standardHeaders: 'draft-7',
legacyHeaders: false,
message: { message: options.message },
store: redis
? new RedisStore({
sendCommand: (...args: string[]) =>
redis.call(args[0], ...args.slice(1)),
})
: undefined,
});
};
// Global limiter — applied to all routes
export const globalLimiter = createRateLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
limit: 200,
message: 'Too many requests. Please try again after 15 minutes.',
});
// Submission limiter — applied to form submission or write endpoints
export const submissionLimiter = createRateLimiter({
windowMs: 10 * 1000, // 10 seconds
limit: 1,
message: 'Too many submissions. Try again after 10 seconds.',
});
express-rate-limit tracks request counts per client identifier (defaulting to req.ip). On each incoming request: