В чём разница между forRoot() и forFeature() при регистрации модулей?
forRoot() конфигурирует глобальный singleton-ресурс (соединение с БД) один раз в AppModule; forFeature() регистрирует конкретные сущности/схемы внутри фича-модуля и предоставляет их репозитории только в нём.
forRoot() и forFeature() в NestJS
Это конвенция для динамических модулей, которая позволяет один раз сконфигурировать глобальный ресурс (forRoot) и многократно подключить его части в отдельных фича-модулях (forFeature). Классические примеры — TypeOrmModule, MongooseModule, SequelizeModule.
forRoot — глобальная конфигурация
forRoot() регистрирует соединение (или другой тяжёлый singleton) один раз в корневом модуле и помечает его global: true, чтобы сделать провайдеры доступными во всём приложении без повторного импорта.
// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'secret',
database: 'app',
autoLoadEntities: true,
synchronize: false,
}),
],
})
export class AppModule {}
forFeature — регистрация сущностей в конкретном модуле
forFeature() принимает список сущностей (или схем) и предоставляет репозитории только внутри текущего модуля через InjectRepository:
// 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])],
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(User)
private readonly userRepo: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.userRepo.find();
}
}
Собственный динамический модуль с forRoot/forFeature
Можно реализовать тот же паттерн самостоятельно:
// cache.module.ts
import { DynamicModule, Global, Module } from '@nestjs/common';
export interface CacheModuleOptions {
ttl: number;
host: string;
}
@Global()
@Module({})
export class CacheModule {
static forRoot(options: CacheModuleOptions): DynamicModule {
return {
module: CacheModule,
providers: [
{ provide: 'CACHE_OPTIONS', useValue: options },
CacheService,
],
exports: [CacheService],
};
}
static forFeature(namespace: string): DynamicModule {
return {
module: CacheModule,
providers: [
{
provide: `CACHE_NAMESPACE_${namespace.toUpperCase()}`,
useFactory: (svc: CacheService) => svc.createNamespace(namespace),
inject: [CacheService],
},
],
exports: [`CACHE_NAMESPACE_${namespace.toUpperCase()}`],
};
}
}
forRootAsync — асинхронная конфигурация
Когда настройки нужно получить из ConfigService, используется forRootAsync:
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: 'postgres',
url: config.get<string>('DATABASE_URL'),
autoLoadEntities: true,
synchronize: false,
}),
})
Подводные камни
- Вызывать
forRoot()дважды с разными конфигурациями нельзя — второй вызов молча перезапишет первый. Для нескольких соединений используй именованные коннекции черезnameопцию. - Если модуль помечен
@Global()и зарегистрирован черезforRoot(), дополнительный импорт в других модулях не нужен. Повторный импорт не ломает, но создаёт путаницу. forFeature()создаёт отдельный токен репозитория —InjectRepository(Entity)работает только в том модуле, который вызвалforFeature([Entity]). Использование репозитория в другом модуле без импорта даст ошибку DI.- Не забывай экспортировать
TypeOrmModuleиз фича-модуля, если дочерний модуль тоже должен инжектировать тот же репозиторий. - При использовании
synchronize: trueвforRootв продакшене — риск потери данных. Всегда выключай в production. - Асинхронная конфигурация через
useFactoryвforRootAsyncвыполняется при старте приложения. Если фабрика бросает исключение, приложение не запустится без внятной ошибки. autoLoadEntities: trueподгружает только сущности, зарегистрированные черезforFeature. Сущности, не включённые ни в один модуль, не будут замечены TypeORM.
Common mistakes
- Дает общий ответ про Node.js и не называет конкретные API NestJS.
- Не объясняет, где в lifecycle находится forRoot() и forFeature().
- Не разделяет validation, authorization, business logic и persistence.
- Игнорирует ошибки, лимиты входных данных, observability и тестирование.
What the interviewer is testing
- Может объяснить forRoot() и forFeature() на примере кода.
- Называет ключевые API: forRoot(), forFeature().
- Использует точные API NestJS, а не вымышленные hooks/decorators/methods.
- Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.