Skip to content

Latest commit

 

History

History
166 lines (129 loc) · 4.64 KB

File metadata and controls

166 lines (129 loc) · 4.64 KB

cachecraft

NestJS Redis TypeScript License: MIT

Advanced Redis caching patterns for NestJS with decorator-based API, stampede prevention, distributed invalidation, and built-in metrics.

Why cachecraft?

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

Features

  • @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

Quick Start

git clone https://github.com/Sachini066/cachecraft.git
cd cachecraft
cp .env.example .env
docker compose up -d

Metrics at http://localhost:3000/metrics/cache | Swagger at http://localhost:3000/docs

Usage

Cache-Aside Pattern

@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.

Write-Through Pattern

@Post()
@WriteThrough({ key: 'product::arg0', ttl: 300 })
create(@Body() dto: CreateProductDto) {
  return this.productsService.create(dto);
}

Cache Invalidation

@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.

Programmatic API

@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
    );
  }
}

XFetch: Stampede Prevention

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.

Metrics

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, ... }
  }
}

Project Structure

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

Testing

npm test              # Unit tests (mocked Redis)
npm run test:cov      # Coverage report

Environment Variables

Variable Default Description
REDIS_URL redis://localhost:6379 Redis connection URL
PORT 3000 Application port

License

MIT