Пошук уроків, статей та іншого контенту
Зрозумієте принцип ін’єкції залежностей і підключите providers через конструктор класу.
Ін’єкція залежностей — це підхід, за якого клас не створює свої залежності самостійно, а отримує готові екземпляри від зовнішнього механізму.
Наприклад, сервіс UsersService може залежати від UsersRepository. Без Dependency Injection сервіс створював би репозиторій вручну:
class UsersService {
private repository = new UsersRepository();
}Такий код створює сильну залежність між класами. UsersService сам вирішує, яку реалізацію використовувати, а тестування стає складнішим.
У NestJS залежність описується в конструкторі:
@Injectable()
export class UsersService {
constructor(private readonly repository: UsersRepository) {}
}NestJS:
аналізує параметри конструктора;
знаходить зареєстрований provider для UsersRepository;
створює його екземпляр;
передає цей екземпляр у конструктор UsersService.
Клас при цьому не відповідає за створення своїх залежностей.
Provider — це клас або інше значення, яке NestJS уміє створювати та передавати іншим класам.
Найчастіше provider — це сервіс із декоратором @Injectable():
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
findAll() {
return ['Alice', 'Bob'];
}
}Декоратор @Injectable() повідомляє NestJS, що клас може брати участь у Dependency Injection.
Однак самого декоратора недостатньо. Provider потрібно зареєструвати в модулі:
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
@Module({
providers: [UsersService],
})
export class UsersModule {}Після цього NestJS зможе створити UsersService і передати його іншим providers або контролерам цього модуля.
Розглянемо модуль із сервісом і контролером.
import { Injectable } from '@nestjs/common';
@Injectable()
export class TasksService {
private readonly tasks = [
{ id: 1, title: 'Вивчити NestJS' },
{ id: 2, title: 'Створити API' },
];
findAll() {
return this.tasks;
}
}import { Controller, Get } from '@nestjs/common';
import { TasksService } from './tasks.service';
@Controller('tasks')
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
@Get()
findAll() {
return this.tasksService.findAll();
}
}TasksController не створює TasksService через new. Залежність вказана в конструкторі:
constructor(private readonly tasksService: TasksService) {}NestJS сам передасть екземпляр TasksService під час створення контролера.
import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';
@Module({
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}Щоб модуль працював у застосунку, його потрібно підключити до кореневого модуля:
import { Module } from '@nestjs/common';
import { TasksModule } from './tasks/tasks.module';
@Module({
imports: [TasksModule],
})
export class AppModule {}Після запуску NestJS endpoint GET /tasks поверне:
[
{
"id": 1,
"title": "Вивчити NestJS"
},
{
"id": 2,
"title": "Створити API"
}
]Структура файлів:
src/
├── app.module.ts
├── main.ts
└── tasks/
├── tasks.controller.ts
├── tasks.module.ts
└── tasks.service.tstasks.service.tsimport { Injectable } from '@nestjs/common';
@Injectable()
export class TasksService {
private readonly tasks = [
{ id: 1, title: 'Вивчити NestJS' },
{ id: 2, title: 'Створити API' },
];
findAll() {
return this.tasks;
}
findById(id: number) {
return this.tasks.find((task) => task.id === id);
}
}tasks.controller.tsimport { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
import { TasksService } from './tasks.service';
@Controller('tasks')
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
@Get()
findAll() {
return this.tasksService.findAll();
}
@Get(':id')
findById(@Param('id', ParseIntPipe) id: number) {
return this.tasksService.findById(id);
}
}tasks.module.tsimport { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';
@Module({
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}app.module.tsimport { Module } from '@nestjs/common';
import { TasksModule } from './tasks/tasks.module';
@Module({
imports: [TasksModule],
})
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 /tasks — отримати всі завдання;
GET /tasks/1 — отримати завдання з ідентифікатором 1.
Provider може залежати від іншого provider. Наприклад, контролер залежить від сервісу, а сервіс — від репозиторію.
@Injectable()
export class TasksRepository {
findAll() {
return [
{ id: 1, title: 'Вивчити NestJS' },
{ id: 2, title: 'Створити API' },
];
}
}Сервіс отримує репозиторій через конструктор:
import { Injectable } from '@nestjs/common';
import { TasksRepository } from './tasks.repository';
@Injectable()
export class TasksService {
constructor(
private readonly tasksRepository: TasksRepository,
) {}
findAll() {
return this.tasksRepository.findAll();
}
}У такому випадку модуль повинен зареєструвати обидва класи:
import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksRepository } from './tasks.repository';
import { TasksService } from './tasks.service';
@Module({
controllers: [TasksController],
providers: [TasksRepository, TasksService],
})
export class TasksModule {}NestJS спочатку створить TasksRepository, потім передасть його в TasksService, а готовий TasksService — у TasksController.
Схема залежностей виглядає так:
TasksController
↓
TasksService
↓
TasksRepositoryProvider доступний лише в тому модулі, де його зареєстровано, якщо модуль не експортує його назовні.
Наприклад, TasksService зареєстрований у TasksModule. Щоб інший модуль міг його використовувати, потрібно:
додати TasksService у exports;
імпортувати TasksModule в інший модуль.
import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';
@Module({
controllers: [TasksController],
providers: [TasksService],
exports: [TasksService],
})
export class TasksModule {}Тепер інший модуль може отримати TasksService через конструктор:
import { Module } from '@nestjs/common';
import { TasksModule } from './tasks/tasks.module';
import { ReportsService } from './reports.service';
@Module({
imports: [TasksModule],
providers: [ReportsService],
})
export class ReportsModule {}import { Injectable } from '@nestjs/common';
import { TasksService } from './tasks/tasks.service';
@Injectable()
export class ReportsService {
constructor(private readonly tasksService: TasksService) {}
getTasksForReport() {
return this.tasksService.findAll();
}
}exports робить provider доступним для модулів, які імпортують поточний модуль. Реєстрація provider у providers та експорт — це різні операції.
readonlyУ конструкторах NestJS часто використовують такий запис:
constructor(private readonly tasksService: TasksService) {}Це скорочений синтаксис TypeScript. Він одночасно:
оголошує параметр конструктора;
створює приватне поле tasksService;
зберігає переданий об’єкт у цьому полі;
забороняє повторно присвоїти інше значення через readonly.
Еквівалентний, але довший запис:
export class TasksController {
private readonly tasksService: TasksService;
constructor(tasksService: TasksService) {
this.tasksService = tasksService;
}
}readonly не робить сам сервіс повністю незмінним. Він лише забороняє замінити посилання в полі контролера.
Якщо клас використовується в конструкторі, але не доданий до providers модуля, NestJS не зможе його створити.
Наприклад:
@Injectable()
export class TasksService {}@Controller('tasks')
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
}Якщо TasksService відсутній у модулі:
@Module({
controllers: [TasksController],
})
export class TasksModule {}застосунок завершиться з помилкою про неможливість розв’язати залежність контролера.
Правильна конфігурація:
@Module({
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}newНе варто створювати provider вручну в іншому provider:
@Injectable()
export class TasksController {
private readonly tasksService = new TasksService();
}Це обходить контейнер залежностей NestJS. Краще передати залежність через конструктор:
@Controller('tasks')
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
}@Injectable()Для сервісу, який має залежності або використовується як звичайний NestJS provider, потрібно використовувати @Injectable():
@Injectable()
export class TasksService {}providersФайл із класом і декоратор @Injectable() самі по собі не реєструють provider у модулі:
@Module({
providers: [TasksService],
})
export class TasksModule {}imports і providersproviders — класи, які створює та керує ними поточний модуль;
imports — інші модулі, функціональність яких потрібна поточному модулю;
exports — providers поточного модуля, доступні імпортованим модулям.
Якщо provider потрібен в іншому модулі, недостатньо лише зареєструвати його:
@Module({
providers: [TasksService],
})
export class TasksModule {}Потрібно також додати його до exports:
@Module({
providers: [TasksService],
exports: [TasksService],
})
export class TasksModule {}Dependency Injection передає залежності класу замість ручного створення через new.
Залежності в NestJS зазвичай вказують у конструкторі.
Клас provider позначають декоратором @Injectable().
Provider потрібно зареєструвати в масиві providers модуля.
Контролер може отримати сервіс через конструктор.
Provider може залежати від іншого provider.
Щоб використовувати provider в іншому модулі, його потрібно експортувати через exports, а модуль — імпортувати через imports.
Dependency Injection зменшує зв’язаність класів і спрощує їх повторне використання та тестування.