Why Redis for Node.js
Redis is a single-threaded in-memory data store that handles 100,000+ operations per second. For Node.js apps, it's the standard solution for shared state across processes: cache, sessions, rate limiting, job queues, and pub/sub messaging.
Connection setup
import { createClient } from 'redis';
const redis = createClient({
url: process.env.REDIS_URL,
socket: {
reconnectStrategy: (retries) => Math.min(retries * 50, 2000),
},
});
redis.on('error', (err) => log.error({ err }, 'Redis error'));
await redis.connect();Caching patterns
// Cache-aside (most common)
async function getUser(id: string) {
const key = `user:${id}`;
const hit = await redis.get(key);
if (hit) return JSON.parse(hit);
const user = await db.user.findUnique({ where: { id } });
await redis.setEx(key, 300, JSON.stringify(user)); // 5min TTL
return user;
}
// Cache stampede prevention (single-flight)
const inflight = new Map<string, Promise<any>>();
async function cachedFetch(key: string, loader: () => Promise<any>) {
if (inflight.has(key)) return inflight.get(key);
const promise = loader().finally(() => inflight.delete(key));
inflight.set(key, promise);
return promise;
}Rate limiting with sorted sets
async function rateLimit(userId: string, limit: number, windowMs: number): Promise<boolean> {
const now = Date.now();
const key = `rl:${userId}`;
const windowStart = now - windowMs;
const pipeline = redis.multi();
pipeline.zRemRangeByScore(key, 0, windowStart); // remove old entries
pipeline.zAdd(key, { score: now, value: `${now}` });
pipeline.zCard(key);
pipeline.expire(key, Math.ceil(windowMs / 1000));
const results = await pipeline.exec();
const count = results[2] as number;
return count <= limit;
}Job queues with BullMQ
import { Queue, Worker } from 'bullmq';
const emailQueue = new Queue('email', { connection: redis });
// Producer
await emailQueue.add('send-welcome', { userId, email }, {
attempts: 3,
backoff: { type: 'exponential', delay: 1000 },
});
// Consumer
new Worker('email', async (job) => {
await sendEmail(job.data);
}, { connection: redis, concurrency: 5 });Pub/Sub for real-time events
// Publisher
await redis.publish('deployments', JSON.stringify({ projectId, status: 'deployed' }));
// Subscriber (needs a dedicated connection)
const subscriber = redis.duplicate();
await subscriber.connect();
await subscriber.subscribe('deployments', (message) => {
const event = JSON.parse(message);
io.to(`project:${event.projectId}`).emit('deployment', event);
});Leaderboards with sorted sets
// Update score
await redis.zAdd('leaderboard:monthly', { score: newScore, value: userId });
// Get top 10
const leaders = await redis.zRangeWithScores('leaderboard:monthly', 0, 9, { REV: true });
// Get user's rank
const rank = await redis.zRevRank('leaderboard:monthly', userId);