Пошук уроків, статей та іншого контенту
Створите сервіс як provider і винесете бізнес-логіку з контролера в окремий клас.
Provider — це клас або інше значення, яке NestJS може створити та передати іншому класу через dependency injection.
Найчастіше provider використовується для:
бізнес-логіки;
роботи з базою даних;
взаємодії із зовнішніми API;
повторного використання спільної логіки в різних частинах застосунку.
Service — це звичайний клас, позначений декоратором @Injectable(). Такий клас можна зареєструвати як provider і впровадити в контролер.
Контролер має відповідати переважно за HTTP-рівень:
отримати запит;
прочитати параметри або тіло запиту;
викликати потрібний метод сервісу;
повернути результат клієнту.
Якщо контролер сам створює об'єкти, змінює дані та виконує всі перевірки, він швидко стає складним для підтримки.
Наприклад, такий контролер поєднує HTTP-логіку та бізнес-логіку:
import { Body, Controller, Post } from '@nestjs/common';
@Controller('tasks')
export class TasksController {
private tasks = [];
@Post()
create(@Body() body: { title: string }) {
const task = {
id: this.tasks.length + 1,
title: body.title,
completed: false,
};
this.tasks.push(task);
return task;
}
}Краще винести роботу із завданнями в окремий сервіс. Тоді контролер залишиться невеликим, а логіку можна буде використовувати в інших класах.
Сервіс можна створити за допомогою Nest CLI:
nest generate service tasksСкорочений варіант команди:
nest g s tasksNest створить файл tasks.service.ts приблизно з таким вмістом:
import { Injectable } from '@nestjs/common';
@Injectable()
export class TasksService {}Декоратор @Injectable() повідомляє NestJS, що клас можна використовувати як provider і впроваджувати в інші класи.
Створимо сервіс, який зберігатиме завдання в масиві:
import { Injectable } from '@nestjs/common';
export interface Task {
id: number;
title: string;
completed: boolean;
}
@Injectable()
export class TasksService {
private readonly tasks: Task[] = [];
findAll(): Task[] {
return this.tasks;
}
create(title: string): Task {
const task: Task = {
id: this.tasks.length + 1,
title,
completed: false,
};
this.tasks.push(task);
return task;
}
complete(id: number): Task | undefined {
const task = this.tasks.find((item) => item.id === id);
if (!task) {
return undefined;
}
task.completed = true;
return task;
}
}У цьому прикладі:
tasks — внутрішній стан сервісу;
findAll() повертає всі завдання;
create() створює та зберігає нове завдання;
complete() позначає завдання як виконане.
Сервіс не працює безпосередньо з HTTP-запитами. Він нічого не знає про @Get(), @Post() або об'єкти запиту. Це робить його незалежнішим від контролера.
Provider потрібно зареєструвати в модулі. Якщо сервіс створено командою nest generate service tasks, Nest CLI зазвичай додає його до масиву providers відповідного модуля.
Файл tasks.module.ts може мати такий вигляд:
import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';
@Module({
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}Масив providers повідомляє NestJS, які provider доступні в цьому модулі.
Якщо сервіс не додати до providers, NestJS не зможе створити його екземпляр для dependency injection.
Щоб отримати сервіс у контролері, потрібно:
Додати сервіс до конструктора контролера.
Вказати тип TasksService.
Викликати методи сервісу в обробниках маршрутів.
import {
Body,
Controller,
Get,
Param,
ParseIntPipe,
Post,
} from '@nestjs/common';
import { Task, TasksService } from './tasks.service';
interface CreateTaskDto {
title: string;
}
@Controller('tasks')
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
@Get()
findAll(): Task[] {
return this.tasksService.findAll();
}
@Post()
create(@Body() body: CreateTaskDto): Task {
return this.tasksService.create(body.title);
}
@Post(':id/complete')
complete(@Param('id', ParseIntPipe) id: number): Task | undefined {
return this.tasksService.complete(id);
}
}Рядок:
constructor(private readonly tasksService: TasksService) {}означає, що NestJS має:
створити екземпляр TasksService;
передати його в конструктор TasksController;
зберегти його у властивості tasksService.
Цей процес називається dependency injection, або впровадження залежностей.
Контролер не створює сервіс вручну через new TasksService(). Створенням і передаванням залежностей займається NestJS.
Створимо застосунок із трьома файлами модуля tasks.
tasks.service.tsimport { Injectable } from '@nestjs/common';
export interface Task {
id: number;
title: string;
completed: boolean;
}
@Injectable()
export class TasksService {
private readonly tasks: Task[] = [];
findAll(): Task[] {
return this.tasks;
}
create(title: string): Task {
const task: Task = {
id: this.tasks.length + 1,
title,
completed: false,
};
this.tasks.push(task);
return task;
}
complete(id: number): Task | undefined {
const task = this.tasks.find((item) => item.id === id);
if (!task) {
return undefined;
}
task.completed = true;
return task;
}
}tasks.controller.tsimport {
Body,
Controller,
Get,
Param,
ParseIntPipe,
Post,
} from '@nestjs/common';
import { Task, TasksService } from './tasks.service';
interface CreateTaskDto {
title: string;
}
@Controller('tasks')
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
@Get()
findAll(): Task[] {
return this.tasksService.findAll();
}
@Post()
create(@Body() body: CreateTaskDto): Task {
return this.tasksService.create(body.title);
}
@Post(':id/complete')
complete(@Param('id', ParseIntPipe) id: number): Task | undefined {
return this.tasksService.complete(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 {}Після запуску застосунку:
npm run start:devможна виконати такі запити.
Створення завдання:
POST /tasks
Content-Type: application/json
{
"title": "Вивчити providers у NestJS"
}Приклад відповіді:
{
"id": 1,
"title": "Вивчити providers у NestJS",
"completed": false
}Отримання всіх завдань:
GET /tasksЗавершення завдання:
POST /tasks/1/completeУ цьому прикладі контролер відповідає за маршрути та HTTP-дані, а TasksService — за створення, пошук і зміну завдань.
У NestJS терміни provider і service пов'язані, але не є повністю однаковими.
Provider — загальніша назва залежності, якою може керувати NestJS.
Service — поширений різновид provider, що зазвичай містить бізнес-логіку.
Provider може бути:
класом;
значенням;
фабрикою;
класом, підключеним через токен.
На початковому етапі найчастіше достатньо використовувати services:
@Injectable()
export class TasksService {}і реєструвати їх у модулі:
@Module({
providers: [TasksService],
})
export class TasksModule {}За замовчуванням provider доступний лише в тому модулі, де його зареєстровано.
Якщо інший модуль має використовувати сервіс, його потрібно:
додати до exports модуля;
імпортувати цей модуль у модуль-споживач.
Наприклад:
import { Module } from '@nestjs/common';
import { TasksService } from './tasks.service';
@Module({
providers: [TasksService],
exports: [TasksService],
})
export class TasksModule {}exports робить TasksService доступним для інших модулів, які імпортують TasksModule.
Для сервісу, який використовується лише контролером цього самого модуля, exports не потрібен.
providersЯкщо сервіс використовується в конструкторі, але не зареєстрований у модулі, NestJS не зможе його впровадити.
Неправильно:
@Module({
controllers: [TasksController],
})
export class TasksModule {}Правильно:
@Module({
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}newНе потрібно створювати сервіс вручну:
const tasksService = new TasksService();Так NestJS не зможе керувати залежностями сервісу. Використовуйте dependency injection через конструктор:
constructor(private readonly tasksService: TasksService) {}Контролер може отримати дані запиту, але операції зі станом і бізнес-правила краще передати сервісу.
Замість великого методу контролера:
@Post()
create(@Body() body: CreateTaskDto) {
// багато логіки створення завдання
}викликайте сервіс:
@Post()
create(@Body() body: CreateTaskDto) {
return this.tasksService.create(body.title);
}@Injectable()Для звичайного сервісу потрібен декоратор @Injectable():
import { Injectable } from '@nestjs/common';
@Injectable()
export class TasksService {}Без нього клас не буде правильно підготовлений для використання dependency injection.
Provider — це залежність, якою керує NestJS.
Service — типовий provider для бізнес-логіки.
Сервіс позначається декоратором @Injectable().
Сервіс потрібно додати до providers модуля.
Залежність впроваджується через конструктор контролера.
Контролер обробляє HTTP-запити, а сервіс виконує бізнес-логіку.
Для використання provider в іншому модулі його потрібно експортувати через exports і підключити модуль через imports.