Advanced Redis caching patterns for NestJS with decorator-based API, stampede prevention, distributed invalidation, and built-in metrics.
Most caching implementations are basic get/set wrappers. cachecraft provides production-grade caching patterns as simple decorators:
- Cache-Aside with XFetch stampede prevention
- Write-Through for cache consistency
- Cache Invalidation via Redis Pub/Sub across instances
@CacheAside()- Check cache first, fetch on miss, auto-populate@WriteThrough()- Write to source, then update cache atomically@InvalidateCache()- Invalidate after mutations- XFetch Algorithm - Probabilistic early revalidation prevents cache stampedes
- Redis Pub/Sub - Distributed invalidation across multiple app instances
- Built-in Metrics - Hit rate, latency, error tracking per key and global
- Docker Ready - docker-compose with Redis included
git clone https://github.com/Sachini066/cachecraft.git
cd cachecraft
cp .env.example .env
docker compose up -dMetrics at http://localhost:3000/metrics/cache | Swagger at http://localhost:3000/docs
@Controller('products')
export class ProductsController {
@Get(':id')
@CacheAside({ key: 'product::arg0', ttl: 300, staleWindow: 60 })
findOne(@Param('id') id: string) {
return this.productsService.findOne(id);
}
}The staleWindow enables XFetch: as the TTL approaches, requests probabilistically refresh the cache in the background, preventing thundering herd on expiry.
@Post()
@WriteThrough({ key: 'product::arg0', ttl: 300 })
create(@Body() dto: CreateProductDto) {
return this.productsService.create(dto);
}@Delete(':id')
@InvalidateCache({ key: 'product::arg0' })
remove(@Param('id') id: string) {
return this.productsService.remove(id);
}Invalidation is broadcast via Redis Pub/Sub - all instances evict the key.
@Injectable()
export class MyService {
constructor(private cache: CacheService) {}
async getData(id: string) {
return this.cache.cacheAside(
`data:${id}`,
() => this.fetchFromDb(id),
300, // TTL seconds
60, // Stale window seconds
);
}
}Traditional caching creates a thundering herd when keys expire - all requests simultaneously hit the database. cachecraft uses the XFetch algorithm:
Cache TTL: |████████████████████████████|░░░░░|
↑ stale window
Probabilistic revalidation
(probability increases as
expiry approaches)
During the stale window, requests that hit the cache have an increasing probability of triggering a background refresh. By the time the key expires, it's already been refreshed.
GET /metrics/cache{
"global": {
"hits": 1523,
"misses": 47,
"errors": 0,
"hitRate": 0.97,
"avgLatencyMs": 0.8,
"totalRequests": 1570
},
"byKey": {
"product:123": { "hits": 89, "misses": 1, "hitRate": 0.99, ... }
}
}src/
├── cache/
│ ├── decorators/ # @CacheAside, @WriteThrough, @InvalidateCache
│ ├── interceptors/ # NestJS interceptors for each pattern
│ ├── interfaces/ # Types and interfaces
│ ├── cache.module.ts # Global cache module
│ ├── cache.service.ts # Core service with Redis + metrics + XFetch
│ └── index.ts # Public API barrel export
├── metrics/
│ ├── metrics.controller.ts # GET /metrics/cache
│ └── metrics.module.ts
├── app.module.ts
└── main.ts
npm test # Unit tests (mocked Redis)
npm run test:cov # Coverage report| Variable | Default | Description |
|---|---|---|
REDIS_URL |
redis://localhost:6379 |
Redis connection URL |
PORT |
3000 |
Application port |