Пошук уроків, статей та іншого контенту
Розберете призначення основних файлів і каталогів NestJS-проєкту та правила організації вихідного коду.
NestJS-проєкт має передбачувану структуру. Це допомагає швидко знаходити потрібний код і розділяти відповідальність між частинами застосунку.
Типовий проєкт, створений за допомогою Nest CLI, може мати такий вигляд:
my-app/
├── src/
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test/
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── package.json
├── tsconfig.json
├── nest-cli.json
└── README.mdГоловний вихідний код застосунку зберігається в каталозі src.
main.tsФайл main.ts є точкою входу в застосунок. Саме з нього NestJS запускає сервер.
У цьому файлі зазвичай виконуються такі дії:
Створюється екземпляр NestJS-застосунку.
Підключається кореневий модуль.
Налаштовується порт.
Запускається HTTP-сервер.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();AppModule передається в NestFactory.create(). Через нього NestJS знаходить усі модулі, контролери та сервіси застосунку.
app.module.tsМодуль описує частину структури застосунку. На верхньому рівні розташований кореневий модуль AppModule.
import { Module } from '@nestjs/common';
@Module({})
export class AppModule {}Декоратор @Module() повідомляє NestJS, що клас є модулем.
У невеликому застосунку AppModule може містити всі потрібні компоненти:
import { Module } from '@nestjs/common';
import { TasksController } from './tasks/tasks.controller';
import { TasksService } from './tasks/tasks.service';
@Module({
controllers: [TasksController],
providers: [TasksService],
})
export class AppModule {}Основні властивості модуля:
controllers — контролери, які належать модулю;
providers — сервіси та інші залежності;
imports — інші модулі, потрібні цьому модулю;
exports — компоненти, які модуль дозволяє використовувати іншим модулям.
У більших застосунках окрему функціональність зазвичай оформлюють у власний модуль, а AppModule лише підключає його.
Контролер відповідає за обробку HTTP-запитів. Він визначає маршрути та викликає потрібну бізнес-логіку.
Приклад контролера:
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();
}
}Декоратор @Controller('tasks') задає базовий шлях контролера. Метод із декоратором @Get() обробляє запити GET /tasks.
Контролер не повинен містити всю логіку застосунку. Його завдання — отримати запит, передати дані сервісу та повернути результат.
Сервіс містить логіку роботи з даними або виконання операцій предметної області.
import { Injectable } from '@nestjs/common';
@Injectable()
export class TasksService {
private readonly tasks = [
{ id: 1, title: 'Вивчити модулі NestJS' },
{ id: 2, title: 'Створити перший контролер' },
];
findAll() {
return this.tasks;
}
}Декоратор @Injectable() дозволяє NestJS створювати екземпляри класу та передавати їх в інші компоненти.
У контролері сервіс отримується через конструктор:
constructor(private readonly tasksService: TasksService) {}Такий підхід називається впровадженням залежностей. Контролер не створює сервіс вручну за допомогою new TasksService(). NestJS робить це самостійно.
Коли застосунок має кілька функціональних частин, зручно групувати файли за функціональністю.
Наприклад:
src/
├── app.module.ts
├── main.ts
└── tasks/
├── tasks.controller.ts
├── tasks.module.ts
└── tasks.service.tsКаталог tasks містить усе, що стосується завдань:
tasks.controller.ts — HTTP-маршрути для завдань;
tasks.service.ts — логіка роботи із завданнями;
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 {}AppModuleimport { Module } from '@nestjs/common';
import { TasksModule } from './tasks/tasks.module';
@Module({
imports: [TasksModule],
})
export class AppModule {}Після цього NestJS знає про TasksController і TasksService через TasksModule.
Нижче наведено структуру та код простого застосунку, який повертає список завдань за адресою GET /tasks.
src/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();src/app.module.tsimport { Module } from '@nestjs/common';
import { TasksModule } from './tasks/tasks.module';
@Module({
imports: [TasksModule],
})
export class AppModule {}src/tasks/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 {}src/tasks/tasks.controller.tsimport { 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();
}
}src/tasks/tasks.service.tsimport { Injectable } from '@nestjs/common';
@Injectable()
export class TasksService {
findAll() {
return [
{ id: 1, title: 'Вивчити структуру NestJS' },
{ id: 2, title: 'Створити модуль' },
];
}
}Після запуску застосунку запит GET http://localhost:3000/tasks поверне:
[
{
"id": 1,
"title": "Вивчити структуру NestJS"
},
{
"id": 2,
"title": "Створити модуль"
}
]У цьому прикладі:
main.ts запускає застосунок;
AppModule підключає TasksModule;
TasksModule об’єднує контролер і сервіс;
TasksController обробляє HTTP-запит;
TasksService повертає дані.
Nest CLI зазвичай створює тестові файли разом із вихідними.
app.controller.spec.tsЦе модульний тест контролера. Його назва закінчується на .spec.ts.
src/
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.tsТестові файли можна зберігати поруч із кодом, який вони перевіряють. Наприклад:
src/tasks/
├── tasks.controller.spec.ts
├── tasks.controller.ts
├── tasks.module.ts
├── tasks.service.spec.ts
└── tasks.service.tstestКаталог test зазвичай призначений для наскрізних тестів. Такі тести перевіряють застосунок як єдине ціле, включно з HTTP-запитами.
test/
├── app.e2e-spec.ts
└── jest-e2e.jsonНа початковому етапі достатньо розуміти різницю:
модульні тести перевіряють окремий клас або компонент;
наскрізні тести перевіряють взаємодію із запущеним застосунком.
package.jsonМістить:
залежності проєкту;
команди для запуску;
інформацію про проєкт;
налаштування тестування.
Наприклад, команда start:dev зазвичай запускає застосунок у режимі розробки з автоматичним перезапуском після змін.
tsconfig.jsonМістить налаштування TypeScript:
режим компіляції;
цільову версію JavaScript;
правила перевірки типів;
параметри роботи з модулями.
nest-cli.jsonМістить налаштування Nest CLI, наприклад каталог вихідного коду та параметри компіляції.
Ці файли не містять бізнес-логіку застосунку. Вони описують, як проєкт збирається, запускається та тестується.
Краще створювати каталог для кожної функціональної частини:
src/
├── users/
├── tasks/
├── products/
└── app.module.tsТакий підхід називають організацією за функціональністю. Усі файли, пов’язані з користувачами, знаходяться в users, а всі файли, пов’язані із завданнями, — у tasks.
Кожен тип компонента має виконувати свою роль:
модуль об’єднує пов’язані компоненти;
контролер працює із запитами;
сервіс містить логіку;
тест перевіряє поведінку компонента;
main.ts запускає застосунок.
Не варто розміщувати всю логіку в контролері або створювати окрему копію сервісу в кожному методі.
Назви файлів зазвичай містять назву компонента та його тип:
users.controller.ts
users.service.ts
users.module.tsНазви класів пишуться у форматі PascalCase:
export class UsersController {}
export class UsersService {}
export class UsersModule {}Так структура файлів і класів залишається передбачуваною.
Якщо весь код додавати безпосередньо в AppModule, він швидко стане складним для підтримки. AppModule краще використовувати як кореневий модуль, який підключає функціональні модулі.
Якщо контролер або сервіс не вказаний у відповідному модулі, NestJS не зможе коректно його використовувати.
@Module({
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}AppModuleНавіть правильно створений TasksModule не буде доступний застосунку, якщо його не додати до imports.
@Module({
imports: [TasksModule],
})
export class AppModule {}Контролер має бути тонким: отримати запит і передати роботу сервісу. Складні операції краще розміщувати в сервісі.
Файл tasks.controller.ts із каталогу src/tasks може імпортувати сервіс так:
import { TasksService } from './tasks.service';Крапка означає поточний каталог. Шлях має відповідати реальному розташуванню файлів.
Файли users, tasks і products не варто складати в один каталог без чіткої структури. У міру зростання проєкту це ускладнює пошук і зміну коду.
src/main.ts є точкою входу та запускає NestJS-сервер.
AppModule — кореневий модуль застосунку.
Контролери обробляють HTTP-запити.
Сервіси містять логіку роботи застосунку.
Модулі об’єднують пов’язані контролери та сервіси.
Функціональність краще групувати в окремих каталогах.
Тести розміщують поруч із кодом або в окремому каталозі test.
Файли конфігурації описують запуск, компіляцію та тестування, але не містять бізнес-логіку.