Back to the blog

Redis: Caching, Sessions and Queues

Using Redis for data caching, session storage, pub/sub, task queues and cache invalidation.

January 5, 2026
11 min read
389 views
MOLOTILO

MOLOTILO DIGITAL

Redis: Caching, Sessions and Queues

What Redis is

Redis is an in-memory data store used for caching, sessions, queues and pub/sub. Redis speed is measured in microseconds, which makes it ideal for high-load applications.

Connecting to Redis

import Redis from 'ioredis';

// Simple connection
const redis = new Redis({
  host: process.env.REDIS_HOST || 'localhost',
  port: parseInt(process.env.REDIS_PORT || '6379'),
  password: process.env.REDIS_PASSWORD,
  db: 0
});

// With a retry strategy
const redis = new Redis({
  host: 'localhost',
  port: 6379,
  retryStrategy: (times) => {
    const delay = Math.min(times * 50, 2000);
    return delay;
  },
  maxRetriesPerRequest: 3
});

// Connection check
redis.on('connect', () => console.log('Redis connected'));
redis.on('error', (err) => console.error('Redis error:', err));

Caching data

// Simple cache
async function getUser(id: string): Promise<User> {
  const cacheKey = `user:${id}`;

  // Check the cache
  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }

  // Load from the DB
  const user = await prisma.user.findUnique({ where: { id } });

  // Store in the cache for 1 hour
  await redis.setex(cacheKey, 3600, JSON.stringify(user));

  return user;
}

// A universal caching function
async function cached<T>(
  key: string,
  ttl: number,
  fn: () => Promise<T>
): Promise<T> {
  const cached = await redis.get(key);
  if (cached) {
    return JSON.parse(cached);
  }

  const data = await fn();
  await redis.setex(key, ttl, JSON.stringify(data));
  return data;
}

// Usage
const products = await cached(
  'products:featured',
  300, // 5 minutes
  () => prisma.product.findMany({ where: { featured: true } })
);

Cache invalidation

// Deleting a specific key
async function updateUser(id: string, data: UpdateUserData) {
  const user = await prisma.user.update({
    where: { id },
    data
  });

  // Invalidate the cache
  await redis.del(`user:${id}`);

  return user;
}

// Deleting by pattern
async function invalidateUserCache(userId: string) {
  const keys = await redis.keys(`user:${userId}:*`);
  if (keys.length > 0) {
    await redis.del(...keys);
  }
}

// Tagged cache
async function setWithTags(key: string, value: any, tags: string[], ttl: number) {
  const pipeline = redis.pipeline();

  pipeline.setex(key, ttl, JSON.stringify(value));

  for (const tag of tags) {
    pipeline.sadd(`tag:${tag}`, key);
    pipeline.expire(`tag:${tag}`, ttl);
  }

  await pipeline.exec();
}

async function invalidateByTag(tag: string) {
  const keys = await redis.smembers(`tag:${tag}`);
  if (keys.length > 0) {
    await redis.del(...keys, `tag:${tag}`);
  }
}

// Usage
await setWithTags(
  `product:${id}`,
  product,
  ['products', `category:${product.categoryId}`],
  3600
);

// Invalidate all products of a category
await invalidateByTag(`category:${categoryId}`);

Sessions

import session from 'express-session';
import RedisStore from 'connect-redis';

// Express sessions in Redis
app.use(session({
  store: new RedisStore({ client: redis }),
  secret: process.env.SESSION_SECRET!,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000 // 24 hours
  }
}));

// Custom session storage
class SessionManager {
  private prefix = 'session:';
  private ttl = 86400; // 24 hours

  async create(userId: string, data: SessionData): Promise<string> {
    const sessionId = crypto.randomUUID();
    const key = this.prefix + sessionId;

    await redis.setex(key, this.ttl, JSON.stringify({
      userId,
      ...data,
      createdAt: Date.now()
    }));

    return sessionId;
  }

  async get(sessionId: string): Promise<SessionData | null> {
    const data = await redis.get(this.prefix + sessionId);
    return data ? JSON.parse(data) : null;
  }

  async destroy(sessionId: string): Promise<void> {
    await redis.del(this.prefix + sessionId);
  }

  async refresh(sessionId: string): Promise<void> {
    await redis.expire(this.prefix + sessionId, this.ttl);
  }
}

Pub/Sub

// Publisher
const publisher = new Redis();

async function publishEvent(channel: string, data: any) {
  await publisher.publish(channel, JSON.stringify(data));
}

// Subscriber
const subscriber = new Redis();

subscriber.subscribe('orders', 'notifications');

subscriber.on('message', (channel, message) => {
  const data = JSON.parse(message);

  switch (channel) {
    case 'orders':
      handleNewOrder(data);
      break;
    case 'notifications':
      sendNotification(data);
      break;
  }
});

// Usage
await publishEvent('orders', {
  orderId: '123',
  userId: 'user-1',
  total: 1500
});

Task queues

import Bull from 'bull';

// Creating a queue
const emailQueue = new Bull('email', {
  redis: {
    host: 'localhost',
    port: 6379
  }
});

// Adding a task
await emailQueue.add('welcome', {
  to: 'user@example.com',
  subject: 'Welcome!',
  template: 'welcome'
}, {
  attempts: 3,
  backoff: {
    type: 'exponential',
    delay: 1000
  },
  removeOnComplete: true
});

// Processing tasks
emailQueue.process('welcome', async (job) => {
  const { to, subject, template } = job.data;
  await sendEmail(to, subject, template);
  return { sent: true };
});

// Events
emailQueue.on('completed', (job, result) => {
  console.log(`Job ${job.id} completed`);
});

emailQueue.on('failed', (job, err) => {
  console.error(`Job ${job.id} failed:`, err);
});

Rate Limiting

async function rateLimit(
  key: string,
  limit: number,
  window: number
): Promise<boolean> {
  const current = await redis.incr(key);

  if (current === 1) {
    await redis.expire(key, window);
  }

  return current <= limit;
}

// Middleware
async function rateLimitMiddleware(req, res, next) {
  const key = `ratelimit:${req.ip}`;
  const allowed = await rateLimit(key, 100, 60); // 100 requests per minute

  if (!allowed) {
    return res.status(429).json({ error: 'Too many requests' });
  }

  next();
}

Conclusion

Redis is an indispensable tool for high-load applications. Caching speeds up responses, sessions scale, queues offload the main process. Proper cache invalidation is the key to data consistency.

Redis is not just a cache. It’s a universal tool for pub/sub, queues, rate limiting and much more.

Enjoyed the article?

Subscribe to our blog so you don’t miss new posts