How to implement caching strategies in NestJS?
Caching in NestJS
NestJS provides a built-in CacheModule that supports multiple cache stores: in-memory, Redis, Memcached, and more.
Setup
bash
npm install @nestjs/cache-manager cache-manager
npm install cache-manager-redis-yet redis # for RedisIn-Memory Cache
typescript
import { CacheModule } from '@nestjs/cache-manager';
@Module({
imports: [
CacheModule.register({
ttl: 60000, // 60 seconds (in ms)
max: 100, // max items in cache
}),
],
})
export class AppModule {}Redis Cache
typescript
import { CacheModule } from '@nestjs/cache-manager';
import { redisStore } from 'cache-manager-redis-yet';
@Module({
imports: [
CacheModule.registerAsync({
useFactory: async () => ({
store: await redisStore({
socket: { host: 'localhost', port: 6379 },
ttl: 60000,
}),
}),
}),
],
})
export class AppModule {}Using CacheInterceptor (Auto-caching)
typescript
import { CacheInterceptor, CacheTTL, CacheKey } from '@nestjs/cache-manager';
@Controller('products')
@UseInterceptors(CacheInterceptor) // Cache all GET endpoints
export class ProductsController {
@Get()
@CacheTTL(30000) // Override TTL — 30 seconds
findAll() {
return this.productsService.findAll();
}
@Get(':id')
@CacheKey('product-detail') // Custom cache key
@CacheTTL(60000)
findOne(@Param('id') id: string) {
return this.productsService.findOne(id);
}
}Manual Cache Management
typescript
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
@Injectable()
export class ProductsService {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
async findOne(id: string) {
// Try cache first
const cached = await this.cacheManager.get(`product:${id}`);
if (cached) return cached;
// Fetch from DB
const product = await this.productRepo.findById(id);
// Store in cache
await this.cacheManager.set(`product:${id}`, product, 60000);
return product;
}
async update(id: string, data: UpdateProductDto) {
const product = await this.productRepo.update(id, data);
// Invalidate cache
await this.cacheManager.del(`product:${id}`);
await this.cacheManager.del('products:list');
return product;
}
async clearAll() {
await this.cacheManager.reset();
}
}Cache-Aside Pattern
typescript
async getOrSet<T>(key: string, factory: () => Promise<T>, ttl?: number): Promise<T> {
const cached = await this.cacheManager.get<T>(key);
if (cached !== undefined && cached !== null) {
return cached;
}
const value = await factory();
await this.cacheManager.set(key, value, ttl);
return value;
}
// Usage
const product = await this.getOrSet(
`product:${id}`,
() => this.productRepo.findById(id),
60000,
);Cache Invalidation Strategies
| Strategy | When to Use |
|---|---|
| TTL-based | Data changes infrequently |
| Write-through | Invalidate/update cache on every write |
| Event-driven | Listen for change events |
| Pattern-based | Clear all keys matching a pattern |
Summary
| Approach | Pros | Cons |
|---|---|---|
| CacheInterceptor | Zero code, automatic | Less control |
| Manual (CACHE_MANAGER) | Full control | More code |
| Cache-Aside helper | Reusable, clean | Need to build |
Production tip: Use Redis for production caching. Set appropriate TTLs based on data volatility. Always invalidate cache on writes. Monitor cache hit/miss ratios to optimize TTLs.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.