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/arch-use-repository-pattern.md
1---2title: Use Repository Pattern for Data Access3impact: HIGH4impactDescription: Decouples business logic from database5tags: architecture, repository, data-access6---78## Use Repository Pattern for Data Access910Create custom repositories to encapsulate complex queries and database logic. This keeps services focused on business logic, makes testing easier with mock repositories, and allows changing database implementations without affecting business code.1112**Incorrect (complex queries in services):**1314```typescript15// Complex queries in services16@Injectable()17export class UsersService {18constructor(19@InjectRepository(User) private repo: Repository<User>,20) {}2122async findActiveWithOrders(minOrders: number): Promise<User[]> {23// Complex query logic mixed with business logic24return this.repo25.createQueryBuilder('user')26.leftJoinAndSelect('user.orders', 'order')27.where('user.isActive = :active', { active: true })28.andWhere('user.deletedAt IS NULL')29.groupBy('user.id')30.having('COUNT(order.id) >= :min', { min: minOrders })31.orderBy('user.createdAt', 'DESC')32.getMany();33}3435// Service becomes bloated with query logic36}37```3839**Correct (custom repository with encapsulated queries):**4041```typescript42// Custom repository with encapsulated queries43@Injectable()44export class UsersRepository {45constructor(46@InjectRepository(User) private repo: Repository<User>,47) {}4849async findById(id: string): Promise<User | null> {50return this.repo.findOne({ where: { id } });51}5253async findByEmail(email: string): Promise<User | null> {54return this.repo.findOne({ where: { email } });55}5657async findActiveWithMinOrders(minOrders: number): Promise<User[]> {58return this.repo59.createQueryBuilder('user')60.leftJoinAndSelect('user.orders', 'order')61.where('user.isActive = :active', { active: true })62.andWhere('user.deletedAt IS NULL')63.groupBy('user.id')64.having('COUNT(order.id) >= :min', { min: minOrders })65.orderBy('user.createdAt', 'DESC')66.getMany();67}6869async save(user: User): Promise<User> {70return this.repo.save(user);71}72}7374// Clean service with business logic only75@Injectable()76export class UsersService {77constructor(private usersRepo: UsersRepository) {}7879async getActiveUsersWithOrders(): Promise<User[]> {80return this.usersRepo.findActiveWithMinOrders(1);81}8283async create(dto: CreateUserDto): Promise<User> {84const existing = await this.usersRepo.findByEmail(dto.email);85if (existing) {86throw new ConflictException('Email already registered');87}8889const user = new User();90user.email = dto.email;91user.name = dto.name;92return this.usersRepo.save(user);93}94}95```9697Reference: [Repository Pattern](https://martinfowler.com/eaaCatalog/repository.html)98