Как интегрировать TypeORM с NestJS?
Интеграция TypeORM с NestJS
NestJS предоставляет официальный пакет @nestjs/typeorm, который оборачивает TypeORM и встраивает его в систему модулей и внедрения зависимостей фреймворка.
Установка
npm install @nestjs/typeorm typeorm pg
Подключение в корневом модуле
В AppModule вызывается TypeOrmModule.forRoot() — это настраивает единственное соединение с базой данных и делает его доступным через DI-контейнер во всём приложении.
// app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
autoLoadEntities: true, // автоматически подхватывает сущности, зарегистрированные через forFeature
synchronize: false, // никогда не включать в production
}),
],
})
export class AppModule {}
Объявление сущности
// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
name: string;
@CreateDateColumn()
createdAt: Date;
}
Регистрация сущности в feature-модуле
// users.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}
Внедрение репозитория в сервис
// users.service.ts
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.userRepository.find();
}
findOne(id: number): Promise<User | null> {
return this.userRepository.findOneBy({ id });
}
async create(dto: CreateUserDto): Promise<User> {
const user = this.userRepository.create(dto);
return this.userRepository.save(user);
}
}
Асинхронная конфигурация через ConfigService
Для продакшн-приложений конфигурацию принято загружать асинхронно:
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
type: 'postgres',
host: config.get('DB_HOST'),
port: config.get<number>('DB_PORT'),
username: config.get('DB_USER'),
password: config.get('DB_PASS'),
database: config.get('DB_NAME'),
autoLoadEntities: true,
synchronize: false,
}),
inject: [ConfigService],
}),
Миграции
Вместо synchronize: true в production используют миграции TypeORM. Для этого создаётся отдельный data-source.ts с настройками, и CLI TypeORM запускается как typeorm migration:run.
Что хочет услышать интервьюер
Знание разницы между TypeOrmModule.forRoot() и TypeOrmModule.forFeature() и понимание, зачем нужны оба
Умение правильно внедрять репозиторий через @InjectRepository() с дженериком сущности
Понимание опасности synchronize: true и использование миграций в production
Знание autoLoadEntities как удобной альтернативы ручному перечислению entities[]
Способность настраивать асинхронную конфигурацию через forRootAsync и ConfigService
Пример: Асинхронная конфигурация TypeORM через ConfigService
// app.module.ts — корневое подключение
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
type: 'postgres',
host: config.get('DB_HOST'),
port: config.get<number>('DB_PORT'),
username: config.get('DB_USER'),
password: config.get('DB_PASS'),
database: config.get('DB_NAME'),
autoLoadEntities: true,
synchronize: false,
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
Пример: Сущность User
// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
name: string;
@CreateDateColumn()
createdAt: Date;
}
Пример: Feature-модуль с регистрацией сущности
// users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
@Module({
imports: [TypeOrmModule.forFeature([User])], // регистрация репозитория в DI-контейнере
providers: [UsersService],
controllers: [UsersController],
exports: [UsersService],
})
export class UsersModule {}
Пример: Сервис с внедрённым репозиторием
// users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
// @InjectRepository обязателен — без него DI не найдёт токен репозитория
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.userRepository.find();
}
findOne(id: number): Promise<User | null> {
return this.userRepository.findOneBy({ id });
}
async create(data: Partial<User>): Promise<User> {
const user = this.userRepository.create(data);
return this.userRepository.save(user);
}
async remove(id: number): Promise<void> {
await this.userRepository.delete(id);
}
}
Типичные ошибки
Оставляют synchronize: true в production, что может привести к потере данных при деплое
Регистрируют сущность только в forRoot entities[], но забывают добавить в forFeature() нужного модуля
Внедряют Repository<User> без декоратора @InjectRepository() — DI-контейнер не может разрешить зависимость
Не используют autoLoadEntities и вручную дублируют массив entities в нескольких местах, забывая обновлять его
Смешивают конфигурацию базы данных прямо в коде вместо вынесения в переменные окружения через ConfigService


