Topic 9 of 55
Caching with Redis
Overview
Redis is an in-memory data store used for caching, session storage, rate limiting, and pub/sub messaging. Caching frequently-accessed data reduces database load by 90%+ and cuts response times from ~200ms to <5ms.
Syntax
javascript
import { createClient } from 'redis';
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
// Basic key-value operations
await redis.set('key', 'value');
await redis.set('key', 'value', { EX: 3600 }); // expire in 1 hour
const value = await redis.get('key');
await redis.del('key');
// Working with objects (serialize with JSON)
await redis.set('user:42', JSON.stringify(userObject));
const user = JSON.parse(await redis.get('user:42') ?? 'null');
// Increment (for rate limiting, counts)
await redis.incr('api:hits');
await redis.expire('api:hits', 60); // reset every minute
// Lists (for queues)
await redis.lPush('job-queue', JSON.stringify(job));
const job = await redis.rPop('job-queue');
// Hash (efficient for partial updates)
await redis.hSet('product:42', { name: 'Laptop', price: 45000 });
const name = await redis.hGet('product:42', 'name');Common Pitfalls
- Cache invalidation is hard — stale data is a real problem. Use short TTLs (5-15 min) and invalidate on updates.
- Redis is in-memory — data is lost on restart unless persistence (RDB/AOF) is configured. Use it for cacheable, non-critical data.
- Interview tip: The three hardest problems in CS: naming things, cache invalidation, and off-by-one errors. Redis makes cache invalidation simpler with TTLs and pub/sub.
Real-World Example
Cache-aside pattern for API responses:
example
javascript
import { createClient } from 'redis';
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
// Cache-Aside Pattern: Check cache first, then DB
async function getProduct(productId: string) {
const cacheKey = `product:${productId}`;
// 1. Check cache
const cached = await redis.get(cacheKey);
if (cached) {
console.log('Cache HIT for', productId);
return JSON.parse(cached);
}
// 2. Cache miss — fetch from DB
console.log('Cache MISS for', productId);
const product = await prisma.product.findUnique({
where: { id: productId },
include: { category: true, images: true },
});
if (!product) return null;
// 3. Store in cache for 5 minutes
await redis.set(cacheKey, JSON.stringify(product), { EX: 300 });
return product;
}
// Rate limiting with Redis sliding window
async function checkRateLimit(userId: string, limit = 100, window = 60): Promise<boolean> {
const key = `rate:${userId}`;
const current = await redis.incr(key);
if (current === 1) await redis.expire(key, window); // set expiry on first request
return current <= limit;
}
// Invalidate cache when product is updated
async function updateProduct(id: string, data: Partial<Product>) {
const product = await prisma.product.update({ where: { id }, data });
await redis.del(`product:${id}`); // invalidate cache
return product;
}