963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
3.4 KiB
3.4 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| Use Caching Strategically | HIGH | Dramatically reduces database load and response times | performance, caching, redis, optimization |
Use Caching Strategically
Implement caching for expensive operations, frequently accessed data, and external API calls. Use NestJS CacheModule with appropriate TTLs and cache invalidation strategies. Don't cache everything - focus on high-impact areas.
Incorrect (no caching or caching everything):
// No caching for expensive, repeated queries
@Injectable()
export class ProductsService {
async getPopular(): Promise<Product[]> {
// Runs complex aggregation query EVERY request
return this.productsRepo
.createQueryBuilder('p')
.leftJoin('p.orders', 'o')
.select('p.*, COUNT(o.id) as orderCount')
.groupBy('p.id')
.orderBy('orderCount', 'DESC')
.limit(20)
.getMany();
}
}
// Cache everything without thought
@Injectable()
export class UsersService {
@CacheKey('users')
@CacheTTL(3600)
@UseInterceptors(CacheInterceptor)
async findAll(): Promise<User[]> {
// Caching user list for 1 hour is wrong if data changes frequently
return this.usersRepo.find();
}
}
Correct (strategic caching with proper invalidation):
// Setup caching module
@Module({
imports: [
CacheModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
stores: [new KeyvRedis(config.get('REDIS_URL'))],
ttl: 60 * 1000, // Default 60s
}),
}),
],
})
export class AppModule {}
// Manual caching for granular control
@Injectable()
export class ProductsService {
constructor(
@Inject(CACHE_MANAGER) private cache: Cache,
private productsRepo: ProductRepository,
) {}
async getPopular(): Promise<Product[]> {
const cacheKey = 'products:popular';
// Try cache first
const cached = await this.cache.get<Product[]>(cacheKey);
if (cached) return cached;
// Cache miss - fetch and cache
const products = await this.fetchPopularProducts();
await this.cache.set(cacheKey, products, 5 * 60 * 1000); // 5 min TTL
return products;
}
// Invalidate cache on changes
async updateProduct(id: string, dto: UpdateProductDto): Promise<Product> {
const product = await this.productsRepo.save({ id, ...dto });
await this.cache.del('products:popular'); // Invalidate
return product;
}
}
// Decorator-based caching with auto-interceptor
@Controller('categories')
@UseInterceptors(CacheInterceptor)
export class CategoriesController {
@Get()
@CacheTTL(30 * 60 * 1000) // 30 minutes - categories rarely change
findAll(): Promise<Category[]> {
return this.categoriesService.findAll();
}
@Get(':id')
@CacheTTL(60 * 1000) // 1 minute
@CacheKey('category')
findOne(@Param('id') id: string): Promise<Category> {
return this.categoriesService.findOne(id);
}
}
// Event-based cache invalidation
@Injectable()
export class CacheInvalidationService {
constructor(@Inject(CACHE_MANAGER) private cache: Cache) {}
@OnEvent('product.created')
@OnEvent('product.updated')
@OnEvent('product.deleted')
async invalidateProductCaches(event: ProductEvent) {
await Promise.all([
this.cache.del('products:popular'),
this.cache.del(`product:${event.productId}`),
]);
}
}
Reference: NestJS Caching