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/perf-optimize-database.md
1---2title: Optimize Database Queries3impact: HIGH4impactDescription: Database queries are typically the largest source of latency5tags: performance, database, queries, optimization6---78## Optimize Database Queries910Select only needed columns, use proper indexes, avoid over-fetching relations, and consider query performance when designing your data access. Most API slowness traces back to inefficient database queries.1112**Incorrect (over-fetching data and missing indexes):**1314```typescript15// Select everything when you need few fields16@Injectable()17export class UsersService {18async findAllEmails(): Promise<string[]> {19const users = await this.repo.find();20// Fetches ALL columns for ALL users21return users.map((u) => u.email);22}2324async getUserSummary(id: string): Promise<UserSummary> {25const user = await this.repo.findOne({26where: { id },27relations: ['posts', 'posts.comments', 'posts.comments.author', 'followers'],28});29// Over-fetches massive relation tree30return { name: user.name, postCount: user.posts.length };31}32}3334// No indexes on frequently queried columns35@Entity()36export class Order {37@Column()38userId: string; // No index - full table scan on every lookup3940@Column()41status: string; // No index - slow status filtering42}43```4445**Correct (select only needed data with proper indexes):**4647```typescript48// Select only needed columns49@Injectable()50export class UsersService {51async findAllEmails(): Promise<string[]> {52const users = await this.repo.find({53select: ['email'], // Only fetch email column54});55return users.map((u) => u.email);56}5758// Use QueryBuilder for complex selections59async getUserSummary(id: string): Promise<UserSummary> {60return this.repo61.createQueryBuilder('user')62.select('user.name', 'name')63.addSelect('COUNT(post.id)', 'postCount')64.leftJoin('user.posts', 'post')65.where('user.id = :id', { id })66.groupBy('user.id')67.getRawOne();68}6970// Fetch relations only when needed71async getFullProfile(id: string): Promise<User> {72return this.repo.findOne({73where: { id },74relations: ['posts'], // Only immediate relation75select: {76id: true,77name: true,78email: true,79posts: {80id: true,81title: true,82},83},84});85}86}8788// Add indexes on frequently queried columns89@Entity()90@Index(['userId'])91@Index(['status'])92@Index(['createdAt'])93@Index(['userId', 'status']) // Composite index for common query pattern94export class Order {95@PrimaryGeneratedColumn('uuid')96id: string;9798@Column()99userId: string;100101@Column()102status: string;103104@CreateDateColumn()105createdAt: Date;106}107108// Always paginate large datasets109@Injectable()110export class OrdersService {111async findAll(page = 1, limit = 20): Promise<PaginatedResult<Order>> {112const [items, total] = await this.repo.findAndCount({113skip: (page - 1) * limit,114take: limit,115order: { createdAt: 'DESC' },116});117118return {119items,120meta: {121page,122limit,123total,124totalPages: Math.ceil(total / limit),125},126};127}128}129```130131Reference: [TypeORM Query Builder](https://typeorm.io/select-query-builder)132