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-риски: безопасность, отказоустойчивость, логирование и тесты.