NestJS architecture, dependency injection, validation, security, errors, testing, persistence, APIs, microservices, and deployment patterns with prioritized rule tiers and companion rule files.
99
100%
Does it follow best practices?
Impact
97%
1.12xAverage score across 2 eval scenarios
Advisory
Suggest reviewing before use
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
evals
scenario-1
scenario-2
rules