Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
40 prioritized NestJS best practices across architecture, DI, security, performance, testing, and microservices.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
rules/db-avoid-n-plus-one.md
1---2title: Avoid N+1 Query Problems3impact: HIGH4impactDescription: N+1 queries are one of the most common performance killers5tags: database, n-plus-one, queries, performance6---78## Avoid N+1 Query Problems910N+1 queries occur when you fetch a list of entities, then make an additional query for each entity to load related data. Use eager loading with `relations`, query builder joins, or DataLoader to batch queries efficiently.1112**Incorrect (lazy loading in loops causes N+1):**1314```typescript15// Lazy loading in loops causes N+116@Injectable()17export class OrdersService {18async getOrdersWithItems(userId: string): Promise<Order[]> {19const orders = await this.orderRepo.find({ where: { userId } });20// 1 query for orders2122for (const order of orders) {23// N additional queries - one per order!24order.items = await this.itemRepo.find({ where: { orderId: order.id } });25}2627return orders;28}29}3031// Accessing lazy relations without loading32@Controller('users')33export class UsersController {34@Get()35async findAll(): Promise<User[]> {36const users = await this.userRepo.find();37// If User.posts is lazy-loaded, serializing triggers N queries38return users; // Each user.posts access = 1 query39}40}41```4243**Correct (use relations for eager loading):**4445```typescript46// Use relations option for eager loading47@Injectable()48export class OrdersService {49async getOrdersWithItems(userId: string): Promise<Order[]> {50// Single query with JOIN51return this.orderRepo.find({52where: { userId },53relations: ['items', 'items.product'],54});55}56}5758// Use QueryBuilder for complex joins59@Injectable()60export class UsersService {61async getUsersWithPostCounts(): Promise<UserWithPostCount[]> {62return this.userRepo63.createQueryBuilder('user')64.leftJoin('user.posts', 'post')65.select('user.id', 'id')66.addSelect('user.name', 'name')67.addSelect('COUNT(post.id)', 'postCount')68.groupBy('user.id')69.getRawMany();70}7172async getActiveUsersWithPosts(): Promise<User[]> {73return this.userRepo74.createQueryBuilder('user')75.leftJoinAndSelect('user.posts', 'post')76.leftJoinAndSelect('post.comments', 'comment')77.where('user.isActive = :active', { active: true })78.andWhere('post.status = :status', { status: 'published' })79.getMany();80}81}8283// Use find options for specific fields84async getOrderSummaries(userId: string): Promise<OrderSummary[]> {85return this.orderRepo.find({86where: { userId },87relations: ['items'],88select: {89id: true,90total: true,91status: true,92items: {93id: true,94quantity: true,95price: true,96},97},98});99}100101// Use DataLoader for GraphQL to batch and cache queries102import DataLoader from 'dataloader';103104@Injectable({ scope: Scope.REQUEST })105export class PostsLoader {106constructor(private postsService: PostsService) {}107108readonly batchPosts = new DataLoader<string, Post[]>(async (userIds) => {109// Single query for all users' posts110const posts = await this.postsService.findByUserIds([...userIds]);111112// Group by userId113const postsMap = new Map<string, Post[]>();114for (const post of posts) {115const userPosts = postsMap.get(post.userId) || [];116userPosts.push(post);117postsMap.set(post.userId, userPosts);118}119120// Return in same order as input121return userIds.map((id) => postsMap.get(id) || []);122});123}124125// In resolver126@ResolveField()127async posts(@Parent() user: User): Promise<Post[]> {128// DataLoader batches multiple calls into single query129return this.postsLoader.batchPosts.load(user.id);130}131132// Enable query logging in development to detect N+1133TypeOrmModule.forRoot({134logging: ['query', 'error'],135logger: 'advanced-console',136});137```138139Reference: [TypeORM Relations](https://typeorm.io/relations)140