NestJSMiddleTechnical

Как NestJS интегрируется с TypeORM или Prisma? Объясните паттерн repository.

TypeORM интегрируется через @nestjs/typeorm с forRoot() для подключения и forFeature() для регистрации репозиториев в модулях; Prisma подключается как глобальный @Injectable() сервис, расширяющий PrismaClient. Оба поддерживают паттерн Repository для изоляции работы с данными.

Интеграция NestJS с TypeORM и Prisma

NestJS предоставляет официальный пакет `@nestjs/typeorm` и community-пакет `@nestjs/prisma` (или используется Prisma напрямую как провайдер). Оба подхода поддерживают паттерн Repository, но реализуют его по-разному.

TypeORM: настройка

// app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './users/user.entity';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DB_HOST,
      port: 5432,
      username: process.env.DB_USER,
      password: process.env.DB_PASS,
      database: process.env.DB_NAME,
      entities: [User],
      synchronize: false, // никогда true в production
      logging: ['query', 'error'],
    }),
  ],
})
export class AppModule {}

TypeORM: Entity и Repository

// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  email: string;

  @Column()
  name: string;

  @CreateDateColumn()
  createdAt: Date;
}

// users.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [TypeOrmModule.forFeature([User])], // регистрирует UserRepository
  providers: [UsersService],
  controllers: [UsersController],
})
export class UsersModule {}

// users.service.ts
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepository: Repository<User>,
  ) {}

  findAll(): Promise<User[]> {
    return this.usersRepository.find();
  }

  findOne(id: string): Promise<User | null> {
    return this.usersRepository.findOneBy({ id });
  }

  async create(dto: CreateUserDto): Promise<User> {
    const user = this.usersRepository.create(dto);
    return this.usersRepository.save(user);
  }
}

Кастомный Repository (TypeORM)

Для сложной бизнес-логики выносить методы из сервиса в кастомный репозиторий:

// users.repository.ts
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';

@Injectable()
export class UsersRepository extends Repository<User> {
  constructor(private readonly dataSource: DataSource) {
    super(User, dataSource.createEntityManager());
  }

  findActiveByEmail(email: string): Promise<User | null> {
    return this.createQueryBuilder('user')
      .where('user.email = :email', { email })
      .andWhere('user.isActive = true')
      .getOne();
  }
}

// Регистрация в модуле:
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsersService, UsersRepository],
})

Prisma: настройка и интеграция

// prisma.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
}

// prisma.module.ts
@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

// users.service.ts с Prisma
@Injectable()
export class UsersService {
  constructor(private readonly prisma: PrismaService) {}

  findAll() {
    return this.prisma.user.findMany();
  }

  findOne(id: string) {
    return this.prisma.user.findUnique({ where: { id } });
  }

  create(dto: CreateUserDto) {
    return this.prisma.user.create({ data: dto });
  }
}

TypeORM vs Prisma: когда что выбирать

  • TypeORM: декораторы на entity, ближе к Active Record, зрелая поддержка миграций через CLI, поддерживает больше БД.
  • Prisma: schema-first подход, отличная типобезопасность из коробки, удобный Prisma Studio для просмотра данных, быстрее для CRUD.
  • Prisma не поддерживает наследование сущностей и complex mapped types так же гибко, как TypeORM.

Подводные камни

  • `synchronize: true` в TypeORM в production уничтожит данные при изменении entity — всегда использовать миграции.
  • `forFeature([User])` нужно добавлять в каждый модуль, где используется `@InjectRepository(User)` — не достаточно одного глобального импорта.
  • Prisma Client генерируется при сборке — если забыть `prisma generate` после изменения схемы, типы будут устаревшими.
  • Транзакции в TypeORM через `QueryRunner` требуют явного `release()` — утечка QueryRunner приведёт к исчерпанию пула соединений.
  • N+1 проблема в TypeORM при использовании `relations` в `find()` — использовать `QueryBuilder` с `leftJoinAndSelect` или Prisma `include`.
  • Prisma не поддерживает `OR`-условия через декораторы — нужен raw query или `$queryRaw` для сложной фильтрации.
  • TypeORM `save()` выполняет SELECT перед INSERT при обновлении — при массовых операциях использовать `insert()` или `upsert()`.

Common mistakes

  • Дает общий ответ про Node.js и не называет конкретные API NestJS.
  • Не объясняет, где в lifecycle находится интеграция TypeORM/Prisma и repository pattern.
  • Не разделяет validation, authorization, business logic и persistence.
  • Игнорирует ошибки, лимиты входных данных, observability и тестирование.

What the interviewer is testing

  • Может объяснить интеграция TypeORM/Prisma и repository pattern на примере кода.
  • Называет ключевые API: Repository, PrismaService.
  • Использует точные API NestJS, а не вымышленные hooks/decorators/methods.
  • Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.

Sources

Related topics