Модули
Что такое модуль
Модуль — контейнер для контроллеров, сервисов и других модулей. Группирует связанный код.
Каждое приложение NestJS — дерево модулей. Корень — AppModule.
Зачем модули
- Изоляция — код фичи вместе.
- Инкапсуляция — что отдавать наружу.
- Переиспользование — модуль можно подключать.
- Ленивая загрузка — в больших приложениях.
- Тестирование — изолированно.
Создание
nest g module users
Что создаст:
import { Module } from '@nestjs/common';
@Module({})
export class UsersModule {}
И подключит в AppModule.
Метаданные @Module
@Module({
imports: [],
controllers: [],
providers: [],
exports: [],
})
export class UsersModule {}
imports— модули, чьи экспорты нужны.controllers— HTTP-контроллеры.providers— сервисы, фабрики.exports— что доступно другим модулям.
Базовый модуль
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
Всё внутри модуля — контроллер + сервис.
Импорт модуля
Модуль A хочет использовать сервис из модуля B:
users.module.ts:
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
orders.module.ts:
import { UsersModule } from '../users/users.module';
@Module({
imports: [UsersModule],
controllers: [OrdersController],
providers: [OrdersService],
})
export class OrdersModule {}
Теперь OrdersService может внедрить UsersService.
Экспорт
Без exports — провайдеры приватны внутри модуля.
@Module({
providers: [UsersService],
exports: [UsersService], // доступен другим
})
export class UsersModule {}
Можно экспортировать:
- Сервисы.
- Другие модули.
Общие модули
Shared module — общие утилиты:
// logger.module.ts
import { Module, Global } from '@nestjs/common';
import { LoggerService } from './logger.service';
@Global()
@Module({
providers: [LoggerService],
exports: [LoggerService],
})
export class LoggerModule {}
@Global() — не надо импортировать в каждом модуле. Только в корневом.
⚠️ Не злоупотребляй — теряется явность.
Feature-модули
Каждая фича — модуль:
src/
├── modules/
│ ├── users/
│ │ ├── users.controller.ts
│ │ ├── users.module.ts
│ │ └── users.service.ts
│ ├── posts/
│ └── auth/
Динамические модули
Настраиваемый модуль:
@Module({})
export class DatabaseModule {
static forRoot(options: DatabaseOptions): DynamicModule {
return {
module: DatabaseModule,
providers: [
{
provide: 'DATABASE_OPTIONS',
useValue: options,
},
DatabaseService,
],
exports: [DatabaseService],
};
}
}
Использование:
@Module({
imports: [
DatabaseModule.forRoot({
host: 'localhost',
port: 5432,
}),
],
})
export class AppModule {}
Так делают TypeOrmModule.forRoot, ConfigModule.forRoot.
Асинхронные модули
Когда конфиг асинхронный:
DatabaseModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
host: config.get('DB_HOST'),
port: config.get<number>('DB_PORT'),
}),
inject: [ConfigService],
}),
Реэкспорт
Модуль может экспортировать импортированные модули:
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class SharedModule {}
Транзитивно даёт доступ.
Порядок инициализации
NestJS сам разрешает зависимости между модулями. Порядок в imports не критичен.
Циклические зависимости — плохо. Разрывай через forwardRef() или события.
Циклические зависимости
⚠️ Модуль A ↔ Модуль B:
// a.module.ts
@Module({
imports: [forwardRef(() => BModule)],
})
export class AModule {}
// b.module.ts
@Module({
imports: [forwardRef(() => AModule)],
})
export class BModule {}
Или разорвать через EventEmitter.
Практика: Users + Posts
users/users.module.ts:
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
posts/posts.service.ts:
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
@Injectable()
export class PostsService {
constructor(private readonly usersService: UsersService) {}
findAll() {
return [
{ id: 1, title: 'Пост', author: this.usersService.findOne(1) },
];
}
}
posts/posts.module.ts:
import { Module } from '@nestjs/common';
import { PostsController } from './posts.controller';
import { PostsService } from './posts.service';
import { UsersModule } from '../users/users.module';
@Module({
imports: [UsersModule],
controllers: [PostsController],
providers: [PostsService],
})
export class PostsModule {}
app.module.ts:
@Module({
imports: [UsersModule, PostsModule],
})
export class AppModule {}
Кастомные провайдеры
Провайдер — не только класс:
useClass
{
provide: LoggerService,
useClass: process.env.NODE_ENV === 'production'
? FileLoggerService
: ConsoleLoggerService,
}
useValue
{
provide: 'API_KEY',
useValue: process.env.API_KEY,
}
useFactory
{
provide: 'DATABASE',
useFactory: async (config: ConfigService) => {
return await createConnection(config.get('DB_URL'));
},
inject: [ConfigService],
}
useExisting
{
provide: 'AliasService',
useExisting: RealService,
}
Инъекция кастомных провайдеров
constructor(
@Inject('API_KEY') private apiKey: string,
@Inject('DATABASE') private db: Database,
) {}
@Inject('токен') — для не-классов.
Практика: config-модуль
// config/config.module.ts
import { Module, Global } from '@nestjs/common';
import { ConfigService } from './config.service';
@Global()
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule {}
config/config.service.ts:
import { Injectable } from '@nestjs/common';
@Injectable()
export class ConfigService {
private config = {
port: Number(process.env.PORT) || 3000,
dbUrl: process.env.DATABASE_URL || '',
jwtSecret: process.env.JWT_SECRET || '',
};
get<T>(key: keyof typeof this.config): T {
return this.config[key] as T;
}
}
Использование в любом сервисе:
constructor(private config: ConfigService) {
const port = this.config.get<number>('port');
}
Проверка
nest g module users
# → создаст users/users.module.ts и подключит в AppModule
Итог
- Модуль — контейнер для фичи.
imports,controllers,providers,exports— метаданные.@Global()— общий модуль.- Dynamic modules — настройка через
forRoot. - Циклические —
forwardRef. - Кастомные провайдеры —
useClass,useValue,useFactory. @Inject— для токенов.
Дальше — контроллеры.