Bablu Kumar Singh
Back to Blog
Backend Development
7 min read
May 10, 2026

Redis Caching Strategies for Backend Developers

Redis Caching Strategies for Backend Developers

If your Node.js API keeps hitting the database for the same data on every request, you are burning compute cycles and adding latency that your users can feel. Redis sits between your application and your database as an in-memory data store, slashing response times from hundreds of milliseconds to single-digit milliseconds.

Why Redis?

Redis stores data in RAM. Reads happen in O(1) for simple keys and O(log N) for sorted sets. It supports strings, hashes, lists, sets, sorted sets, and streams — making it far more versatile than a simple key-value cache.

Cache-Aside (Lazy Loading)

The most common pattern. The application checks Redis first; on a miss it reads from the database, writes the result to Redis, and returns:

typescript Code Block
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

async function getUserById(userId: string) {
  const cacheKey = `user:${userId}`;

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

  // 2. Cache miss — query the database
  const user = await User.findById(userId).lean();
  if (!user) return null;

  // 3. Populate cache with a TTL (e.g. 10 minutes)
  await redis.set(cacheKey, JSON.stringify(user), 'EX', 600);

  return user;
}

Pros: Only requested data enters the cache; cold-start is graceful.

Cons: The first request for every key is always slow (cache miss).

Write-Through Caching

In write-through, you update the cache at the same time you write to the database. This ensures the cache is always warm and fresh:

typescript Code Block
async function updateUser(userId: string, updates: Partial<IUser>) {
  // 1. Write to database
  const user = await User.findByIdAndUpdate(userId, updates, { new: true }).lean();

  // 2. Immediately update cache
  const cacheKey = `user:${userId}`;
  await redis.set(cacheKey, JSON.stringify(user), 'EX', 600);

  return user;
}

This eliminates stale reads after mutations, but adds a write to Redis on every update — acceptable for entities that are read far more often than they are written.

Cache Invalidation

The two hardest problems in computer science: cache invalidation, naming things, and off-by-one errors. Strategies I use:

Time-Based Expiry (TTL)

Set a TTL on every key. Even if invalidation logic has a bug, stale data expires automatically:

typescript Code Block
await redis.set('settings:global', JSON.stringify(settings), 'EX', 3600);

Event-Driven Invalidation

When a mutation occurs, explicitly delete the affected keys:

typescript Code Block
async function deleteUser(userId: string) {
  await User.findByIdAndDelete(userId);
  await redis.del(`user:${userId}`);
}

Pattern-Based Invalidation

For clearing all keys matching a pattern (e.g., all dashboard data for a tenant):

typescript Code Block
async function invalidateTenantCache(tenantId: string) {
  const keys = await redis.keys(`tenant:${tenantId}:*`);
  if (keys.length > 0) {
    await redis.del(...keys);
  }
}

> Warning: KEYS blocks the Redis event loop on large datasets. In production, use SCAN with a cursor instead.

Distributed Locking with Redlock

When multiple pods process the same job, you need a distributed lock to prevent double-processing. The Redlock algorithm provides a safe mutual exclusion primitive:

typescript Code Block
import Redlock from 'redlock';

const redlock = new Redlock([redis], {
  retryCount: 3,
  retryDelay: 200,
});

async function processPayment(paymentId: string) {
  const lock = await redlock.acquire([`lock:payment:${paymentId}`], 5000);
  try {
    // Critical section — only one pod executes this
    const payment = await Payment.findById(paymentId);
    if (payment.status !== 'pending') return;

    await chargeStripe(payment);
    payment.status = 'completed';
    await payment.save();
  } finally {
    await lock.release();
  }
}

Session Storage

Storing Express sessions in Redis makes your API stateless across pods:

typescript Code Block
import session from 'express-session';
import RedisStore from 'connect-redis';

app.use(
  session({
    store: new RedisStore({ client: redis }),
    secret: process.env.SESSION_SECRET!,
    resave: false,
    saveUninitialized: false,
    cookie: { maxAge: 86400000 }, // 1 day
  })
);

Monitoring Cache Health

Track these metrics to avoid cache-related outages:

  • Hit rate — should stay above 90 %. A low hit rate means your TTLs are too short or keys are being evicted prematurely.
  • Memory usage — set maxmemory and a maxmemory-policy (e.g., allkeys-lru) to prevent Redis from consuming all available RAM.
  • Eviction count — rising evictions indicate you need more memory or smarter TTLs.

Key Takeaways

  • Cache-aside is the safest starting point for most read-heavy workloads.
  • Pair every cache write with a TTL — stale data is worse than a cache miss.
  • Use Redlock when multiple workers compete for the same resource.
  • Monitor hit rates and evictions so caching remains a net benefit, not a liability.

Redis is deceptively simple to set up but requires discipline to operate well. Apply these strategies, and your APIs will feel instantaneous.

#Redis#Caching#Performance#Node.js
Bablu Kumar Singh
Written by

Bablu Kumar Singh

Backend-Focused Full Stack Developer

Backend-Focused Full Stack Developer specializing in Node.js, MongoDB, PostgreSQL, Redis, RabbitMQ, AWS, Docker, System Design, and React Native.

You May Also Like

Designing a Role-Based Access Control (RBAC) System from Scratch
Backend Development
5 min read

Designing a Role-Based Access Control (RBAC) System from Scratch

A blueprint on implementing hierarchical permission matrices in Express.js middleware using bitwise operations and MongoDB caching.

May 24, 2026Read
Authentication Systems in Node.js: JWT, Refresh Tokens, and Security
Backend Development
7 min read

Authentication Systems in Node.js: JWT, Refresh Tokens, and Security

Build a production-grade authentication system in Node.js — covering JWT access tokens, refresh token rotation, password hashing with bcrypt, and common security pitfalls to avoid.

May 28, 2026Read
Building SaaS Platforms with Node.js: Architecture to Payment Integration
Backend Development
8 min read

Building SaaS Platforms with Node.js: Architecture to Payment Integration

End-to-end guide for building multi-tenant SaaS applications with Node.js — covering tenant isolation, subscription billing with Stripe, webhook handling, and production deployment.

May 19, 2026Read