All posts
ScalingMay 2, 2026·7 min read

Using Redis for High Performance Node.js

Caching, queues, pub/sub, leaderboards, rate limiting — the six Redis data structures and the Node.js patterns that make them powerful.

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);

Ready to put this into practice?

Deploy your Node.js app to production in minutes — zero YAML, automatic CI/CD, and HTTPS included.