Пошук уроків, статей та іншого контенту
Налаштуєте взаємодію модулів через imports та exports і повторно використаєте providers.
Модуль у NestJS об’єднує пов’язані контролери, providers та інші модулі. За замовчуванням providers модуля доступні лише всередині цього модуля.
Це означає, що provider, оголошений у UsersModule, не можна безпосередньо інжектити в ReportsModule, доки ми явно не налаштуємо взаємодію між модулями.
Для цього використовують:
exports — робить provider або модуль доступним для інших модулів;
imports — підключає модуль, експортовані providers якого потрібно використати.
Загальна схема:
UsersModule
└── експортує UsersService
↓
ReportsModule
└── імпортує UsersModule
використовує UsersServiceexportsРозглянемо модуль користувачів:
import { Injectable } from '@nestjs/common';
import { Module } from '@nestjs/common';
@Injectable()
export class UsersService {
findAll() {
return [
{ id: 1, name: 'Олена' },
{ id: 2, name: 'Андрій' },
];
}
}
@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}UsersService додано до двох масивів:
providers: [UsersService],
exports: [UsersService],providers реєструє сервіс у модулі, а exports дозволяє іншим модулям отримувати цей сервіс через імпорт UsersModule.
Якщо не додати UsersService до exports, він залишиться внутрішньою реалізацією UsersModule.
importsТепер створимо модуль звітів, якому потрібен UsersService:
import { Controller, Get, Injectable, Module } from '@nestjs/common';
import { UsersModule, UsersService } from '../users/users.module';
@Injectable()
export class ReportsService {
constructor(private readonly usersService: UsersService) {}
getUsersReport() {
const users = this.usersService.findAll();
return {
total: users.length,
users,
};
}
}
@Controller('reports')
export class ReportsController {
constructor(private readonly reportsService: ReportsService) {}
@Get('users')
getUsersReport() {
return this.reportsService.getUsersReport();
}
}
@Module({
imports: [UsersModule],
controllers: [ReportsController],
providers: [ReportsService],
})
export class ReportsModule {}ReportsModule імпортує UsersModule:
imports: [UsersModule],Через це ReportsService може отримати експортований UsersService у конструкторі:
constructor(private readonly usersService: UsersService) {}Важливо: у ReportsModule не потрібно повторно додавати UsersService до providers. Він уже наданий UsersModule, який імпортовано та який експортує цей provider.
Структура файлів:
src/
├── app.module.ts
├── main.ts
├── reports/
│ ├── reports.controller.ts
│ ├── reports.module.ts
│ └── reports.service.ts
└── users/
├── users.module.ts
└── users.service.tsusers/users.service.tsimport { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
findAll() {
return [
{ id: 1, name: 'Олена' },
{ id: 2, name: 'Андрій' },
{ id: 3, name: 'Марія' },
];
}
}users/users.module.tsimport { Module } from '@nestjs/common';
import { UsersService } from './users.service';
@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}reports/reports.service.tsimport { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
@Injectable()
export class ReportsService {
constructor(private readonly usersService: UsersService) {}
getUsersReport() {
const users = this.usersService.findAll();
return {
total: users.length,
users,
};
}
}reports/reports.controller.tsimport { Controller, Get } from '@nestjs/common';
import { ReportsService } from './reports.service';
@Controller('reports')
export class ReportsController {
constructor(private readonly reportsService: ReportsService) {}
@Get('users')
getUsersReport() {
return this.reportsService.getUsersReport();
}
}reports/reports.module.tsimport { Module } from '@nestjs/common';
import { UsersModule } from '../users/users.module';
import { ReportsController } from './reports.controller';
import { ReportsService } from './reports.service';
@Module({
imports: [UsersModule],
controllers: [ReportsController],
providers: [ReportsService],
})
export class ReportsModule {}app.module.tsimport { Module } from '@nestjs/common';
import { ReportsModule } from './reports/reports.module';
@Module({
imports: [ReportsModule],
})
export class AppModule {}main.tsimport { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();Після запуску застосунку запит:
GET http://localhost:3000/reports/usersповерне:
{
"total": 3,
"users": [
{
"id": 1,
"name": "Олена"
},
{
"id": 2,
"name": "Андрій"
},
{
"id": 3,
"name": "Марія"
}
]
}У цьому прикладі:
UsersModule реєструє UsersService.
UsersModule експортує UsersService.
ReportsModule імпортує UsersModule.
ReportsService отримує UsersService через dependency injection.
AppModule імпортує ReportsModule.
Модуль може експортувати кілька providers:
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersRepository } from './users.repository';
@Module({
providers: [UsersService, UsersRepository],
exports: [UsersService, UsersRepository],
})
export class UsersModule {}Експортувати потрібно лише ті залежності, які справді мають використовувати інші модулі. Не обов’язково робити публічними всі providers модуля.
Наприклад, репозиторій можна залишити внутрішнім:
@Module({
providers: [UsersService, UsersRepository],
exports: [UsersService],
})
export class UsersModule {}У такому випадку інші модулі зможуть використовувати UsersService, але не зможуть напряму отримати UsersRepository.
Модуль може експортувати інший імпортований модуль. Це дає змогу створити публічну точку доступу для кількох внутрішніх модулів.
import { Module } from '@nestjs/common';
import { UsersModule } from '../users/users.module';
@Module({
imports: [UsersModule],
exports: [UsersModule],
})
export class AccountsModule {}Тепер модуль, який імпортує AccountsModule, також отримає доступ до provider-ів, експортованих UsersModule.
@Module({
imports: [AccountsModule],
})
export class ReportsModule {}Таку схему варто використовувати обережно. Експортуйте модуль лише тоді, коли він справді є частиною публічного інтерфейсу іншого модуля.
providers, imports та exportsprovidersРеєструє providers у поточному модулі:
@Module({
providers: [UsersService],
})
export class UsersModule {}exportsВідкриває provider для модулів, які імпортують поточний модуль:
@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}importsПідключає модулі, чиї експортовані providers потрібні поточному модулю:
@Module({
imports: [UsersModule],
})
export class ReportsModule {}Ці властивості працюють разом:
@Module({
imports: [UsersModule],
providers: [ReportsService],
})
export class ReportsModule {}ReportsModule може використати UsersService лише тому, що:
UsersModule додав UsersService до providers;
UsersModule додав UsersService до exports;
ReportsModule додав UsersModule до imports.
Коли один модуль має використовувати provider іншого модуля:
Додайте provider до providers модуля-власника.
Додайте цей provider до exports модуля-власника.
Додайте модуль-власник до imports модуля-споживача.
Інжектуйте provider у конструкторі потрібного класу.
Наприклад:
// Модуль-власник
@Module({
providers: [EmailService],
exports: [EmailService],
})
export class NotificationsModule {}// Модуль-споживач
@Module({
imports: [NotificationsModule],
providers: [UsersService],
})
export class UsersModule {}@Injectable()
export class UsersService {
constructor(private readonly emailService: EmailService) {}
}exports@Module({
providers: [UsersService],
})
export class UsersModule {}Якщо інший модуль імпортує UsersModule, але UsersService не експортується, NestJS не зможе інжектити його в цьому модулі.
Правильно:
@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}У imports потрібно додавати модулі, а не providers:
@Module({
imports: [UsersService],
})
export class ReportsModule {}Це неправильна конфігурація. Правильно імпортувати модуль:
@Module({
imports: [UsersModule],
})
export class ReportsModule {}Не потрібно дублювати provider:
@Module({
imports: [UsersModule],
providers: [UsersService, ReportsService],
})
export class ReportsModule {}Якщо UsersService належить UsersModule, достатньо:
@Module({
imports: [UsersModule],
providers: [ReportsService],
})
export class ReportsModule {}Повторна реєстрація може створити окремий екземпляр provider і порушити очікувану область відповідальності.
importsНавіть якщо provider експортується, модуль-споживач має імпортувати модуль-власник:
@Module({
providers: [ReportsService],
})
export class ReportsModule {}Правильно:
@Module({
imports: [UsersModule],
providers: [ReportsService],
})
export class ReportsModule {}Модуль має бути доступним через ланцюжок імпортів від кореневого AppModule:
@Module({
imports: [ReportsModule],
})
export class AppModule {}Якщо модуль не підключено до дерева застосунку, NestJS не створить його контекст і його providers не працюватимуть.
Providers за замовчуванням доступні лише всередині власного модуля.
providers реєструє залежність у модулі.
exports відкриває provider для інших модулів.
imports підключає модулі та їхні експортовані providers.
Provider потрібно оголошувати в providers лише модуля-власника.
Для повторного використання provider інший модуль імпортує модуль-власник, а не реєструє provider повторно.
Така структура допомагає зберігати межі відповідальності модулів і повторно використовувати спільну логіку.