Multi-Layer Cache
```typescript
// cache.ts
import { Redis } from 'ioredis';
import { LRUCache } from 'lru-cache';
interface CacheOptions {
ttl?: number; // Time to live in seconds
staleWhileRevalidate?: number;
tags?: string[]; // For invalidation
}
class MultiLayerCache {
private memory: LRUCache;
private redis: Redis;
constructor(redis: Redis) {
this.redis = redis;
this.memory = new LRUCache({
max: 1000,
ttl: 60 * 1000, // 1 minute default
});
}
async get(key: string): Promise {
// Layer 1: Memory
const memoryHit = this.memory.get(key);
if (memoryHit && memoryHit.expires > Date.now()) {
return memoryHit.value as T;
}
// Layer 2: Redis
const redisValue = await this.redis.get(key);
if (redisValue) {
const parsed = JSON.parse(redisValue) as T;
// Populate memory cache
this.memory.set(key, { value: parsed, expires: Date.now() + 60000 });
return parsed;
}
return null;
}
async set(key: string, value: T, options: CacheOptions = {}): Promise {
const ttl = options.ttl || 3600; // 1 hour default
// Set in Redis
await this.redis.setex(key, ttl, JSON.stringify(value));
// Set in memory (shorter TTL)
this.memory.set(key, {
value,
expires: Date.now() + Math.min(ttl * 1000, 60000),
});
// Track tags for invalidation
if (options.tags) {
for (const tag of options.tags) {
await this.redis.sadd(cache:tag:${tag}, key);
}
}
}
async getOrSet(
key: string,
fetcher: () => Promise,
options: CacheOptions = {}
): Promise {
const cached = await this.get(key);
if (cached !== null) {
return cached;
}
// Prevent cache stampede with lock
const lockKey = lock:${key};
const acquired = await this.redis.set(lockKey, '1', 'EX', 10, 'NX');
if (!acquired) {
// Another process is fetching, wait and retry
await new Promise(resolve => setTimeout(resolve, 100));
return this.getOrSet(key, fetcher, options);
}
try {
const value = await fetcher();
await this.set(key, value, options);
return value;
} finally {
await this.redis.del(lockKey);
}
}
async invalidate(key: string): Promise {
this.memory.delete(key);
await this.redis.del(key);
}
async invalidateByTag(tag: string): Promise {
const keys = await this.redis.smembers(cache:tag:${tag});
if (keys.length > 0) {
await this.redis.del(...keys);
for (const key of keys) {
this.memory.delete(key);
}
}
await this.redis.del(cache:tag:${tag});
}
}
export { MultiLayerCache, CacheOptions };
```
Cache-Aside Pattern
```typescript
// user-service.ts
class UserService {
constructor(private cache: MultiLayerCache) {}
async getUser(id: string): Promise {
return this.cache.getOrSet(
user:${id},
async () => {
return db.users.findUnique({ where: { id } });
},
{ ttl: 3600, tags: ['users', user:${id}] }
);
}
async updateUser(id: string, data: Partial): Promise {
const user = await db.users.update({
where: { id },
data,
});
// Invalidate cache
await this.cache.invalidate(user:${id});
return user;
}
async deleteUser(id: string): Promise {
await db.users.delete({ where: { id } });
await this.cache.invalidateByTag(user:${id});
}
}
```
HTTP Caching Middleware
```typescript
// http-cache-middleware.ts
import { Request, Response, NextFunction } from 'express';
interface HttpCacheOptions {
maxAge?: number;
sMaxAge?: number;
staleWhileRevalidate?: number;
private?: boolean;
vary?: string[];
}
function httpCache(options: HttpCacheOptions = {}) {
return (req: Request, res: Response, next: NextFunction) => {
const directives: string[] = [];
if (options.private) {
directives.push('private');
} else {
directives.push('public');
}
if (options.maxAge !== undefined) {
directives.push(max-age=${options.maxAge});
}
if (options.sMaxAge !== undefined) {
directives.push(s-maxage=${options.sMaxAge});
}
if (options.staleWhileRevalidate !== undefined) {
directives.push(stale-while-revalidate=${options.staleWhileRevalidate});
}
res.setHeader('Cache-Control', directives.join(', '));
if (options.vary) {
res.setHeader('Vary', options.vary.join(', '));
}
next();
};
}
// Usage
app.get('/api/products',
httpCache({ maxAge: 60, sMaxAge: 300, staleWhileRevalidate: 86400 }),
async (req, res) => {
const products = await getProducts();
res.json(products);
}
);
```