Тема 7. Фреймворк NestJS. Контролери та маршрутизація

Контролери: основи маршрутизації

Декоратор @Controller, визначення маршрутів, роль контролера

Контролери: основи маршрутизації

🎯 Мета лекції

  • Зрозуміти роль контролерів як шару обробки HTTP-запитів у NestJS
  • Опанувати використання декоратора @Controller для визначення базових маршрутів
  • Навчитися створювати методи контролера для обробки різних запитів
  • Засвоїти принципи розділення відповідальностей між контролерами та сервісами
  • Вивчити автоматичну серіалізацію відповідей та роботу з асинхронним кодом
  • Практикувати генерацію контролерів через NestJS CLI

🔑 Ключові терміни

  • Controller (контролер): клас, відповідальний за обробку вхідних HTTP-запитів та формування відповідей
  • Route Handler (обробник маршруту): метод контролера, що обробляє конкретний HTTP-запит
  • Request-Response Cycle (цикл запит-відповідь): послідовність обробки від отримання запиту до відправлення відповіді
  • Serialization (серіалізація): процес перетворення об'єктів JavaScript у JSON для передачі по мережі
  • Thin Controller Pattern (патерн тонкого контролера): принцип, за яким контролер містить мінімум логіки

Що таке контролер: перший контактний шар застосунку

Контролери (controllers) є фундаментальним компонентом архітектури NestJS, що відповідає за обробку вхідних HTTP-запитів від клієнтів та формування відповідних HTTP-відповідей. Контролер можна розглядати як «вхідні двері» застосунку — саме через нього зовнішній світ взаємодіє з бізнес-логікою системи.

У класичній MVC-архітектурі (Model-View-Controller) контролер займає проміжну позицію між представленням (view, у випадку API — це клієнтський застосунок) та моделлю (model, бізнес-логіка та дані). Проте в контексті RESTful API, який будується на NestJS, роль контролера дещо специфічніша — він не генерує HTML-шаблони, а повертає структуровані дані в форматі JSON або XML.

Ключова філософія контролерів у NestJS полягає в принципі єдиної відповідальності (Single Responsibility Principle): контролер має відповідати виключно за координацію HTTP-комунікації. Він не містить бізнес-логіки, не виконує складних обчислень, не взаємодіє безпосередньо з базою даних. Замість цього контролер делегує всі змістовні операції сервісам (services), залишаючи собі лише технічні аспекти HTTP-протоколу.

Loading diagram...
sequenceDiagram
    participant C as Клієнт<br/>(Browser/Mobile)
    participant Ctrl as Контролер<br/>(HTTP Layer)
    participant Svc as Сервіс<br/>(Business Logic)
    participant DB as База даних

    C->>Ctrl: HTTP Request<br/>GET /users/123
    Note over Ctrl: Розпарсити запит<br/>Екстрагувати ID
    Ctrl->>Svc: getUserById(123)
    Note over Svc: Виконати бізнес-логіку<br/>Застосувати правила
    Svc->>DB: SELECT * FROM users WHERE id=123
    DB-->>Svc: User data
    Svc-->>Ctrl: User object
    Note over Ctrl: Серіалізувати об'єкт<br/>Встановити HTTP-статус
    Ctrl-->>C: HTTP Response<br/>200 OK + JSON

    style C fill:#64748b,stroke:#334155,color:#ffffff
    style Ctrl fill:#f59e0b,stroke:#b45309,color:#ffffff
    style Svc fill:#22c55e,stroke:#15803d,color:#ffffff
    style DB fill:#3b82f6,stroke:#1d4ed8,color:#ffffff

Контролер виконує кілька чітко визначених функцій у циклі обробки запиту:

  1. Маршрутизація: Визначення, який саме метод має обробити конкретний запит на основі URL та HTTP-методу
  2. Екстракція даних: Витягування необхідної інформації з різних частин HTTP-запиту (параметри URL, query string, тіло запиту, заголовки)
  3. Валідація вхідних даних: Координація процесу перевірки коректності отриманих даних (делегується pipes)
  4. Делегування бізнес-логіки: Виклик відповідних методів сервісів для виконання змістовних операцій
  5. Формування відповіді: Серіалізація результатів у JSON та встановлення відповідного HTTP-статусу
Контролер не «робить» бізнес-логіку — він «координує» її виконання. Ця тонка, але принципова відмінність забезпечує чистоту архітектури та можливість повторного використання бізнес-логіки в різних контекстах (HTTP API, GraphQL, консольні команди, обробники черг повідомлень).

Декоратор @Controller: визначення базового маршруту

Щоб позначити клас як контролер у NestJS, використовується декоратор @Controller(). Цей декоратор виконує дві критичні функції: по-перше, він повідомляє фреймворку, що клас є контролером і має бути зареєстрований у системі маршрутизації; по-друге, він дозволяє визначити базовий префікс шляху (path prefix) для всіх маршрутів цього контролера.

Базовий синтаксис декоратора

Декоратор @Controller() може використовуватися як з аргументом (префіксом шляху), так і без нього:

import { Controller } from '@nestjs/common';

@Controller()
export class AppController {
  // Маршрути цього контролера будуть доступні від кореня: /
}

Коли декоратор викликається з аргументом-рядком (наприклад, @Controller('users')), цей рядок стає базовим шляхом для всіх маршрутів, визначених у методах контролера. Якщо метод контролера визначає власний шлях, він буде додано до базового префікса, формуючи повний URL маршруту.

Формування повних шляхів маршрутів

Повний шлях до ендпоінту формується з кількох компонентів:

Повний URL = [Глобальний префікс] + [Префікс контролера] + [Шлях методу]

Наприклад:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api'); // Глобальний префікс
  await app.listen(3000);
}
bootstrap();

У цьому прикладі:

  • Глобальний префікс: api (налаштовано в main.ts)
  • Префікс контролера: users (визначено в @Controller('users'))
  • Шлях методу: profile (визначено в @Get('profile'))
  • Результуючий URL: /api/users/profile
Використання префіксів контролера забезпечує логічне групування ендпоінтів за ресурсами або доменами. Наприклад, всі операції з користувачами групуються під /users, всі операції з продуктами — під /products, що створює інтуїтивну та передбачувану структуру API.

Версіювання через префікси

Префікси контролера часто використовуються для версіювання API:

@Controller('v1/users')
export class UsersV1Controller {
  // Ендпоінти версії 1: /v1/users/*
}

@Controller('v2/users')
export class UsersV2Controller {
  // Ендпоінти версії 2: /v2/users/*
}

Це дозволяє підтримувати кілька версій API одночасно, забезпечуючи зворотну сумісність для існуючих клієнтів під час впровадження нових функцій.

Структура класу контролера: анатомія компонента

Контролер у NestJS — це TypeScript-клас, позначений декоратором @Controller(). Структура типового контролера включає кілька стандартних елементів, кожен з яких має своє призначення.

Мінімальний контролер

Найпростіший функціональний контролер складається з класу з декоратором та хоча б одного методу-обробника:

import { Controller, Get } from '@nestjs/common';

@Controller('health')
export class HealthController {
  @Get()
  check() {
    return { status: 'ok', timestamp: new Date().toISOString() };
  }
}

Цей контролер обробляє GET-запити до /health та повертає статус здоров'я системи. Незважаючи на свою простоту, він демонструє всі базові концепції: декоратор класу, декоратор методу та повернення даних.

Контролер з впровадженням залежностей

У реальних застосунках контролери рідко працюють ізольовано — вони потребують сервісів для виконання бізнес-логіки. Залежності впроваджуються через конструктор класу:

import { Controller, Get } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  // Впровадження залежностей через конструктор
  constructor(private readonly usersService: UsersService) {}

  @Get()
  findAll() {
    return this.usersService.findAll();
  }
}

Модифікатори private readonly у параметрі конструктора виконують подвійну функцію в TypeScript:

  1. private: Автоматично створює приватну властивість класу з такою самою назвою
  2. readonly: Забороняє зміну значення властивості після ініціалізації

Це скорочений синтаксис, еквівалентний наступному розгорнутому коду:

export class UsersController {
  private readonly usersService: UsersService;

  constructor(usersService: UsersService) {
    this.usersService = usersService;
  }
}
NestJS автоматично розпізнає типи залежностей у конструкторі та впровадить відповідні екземпляри з IoC-контейнера. Розробнику не потрібно вручну створювати екземпляри сервісів — фреймворк керує всім життєвим циклом об'єктів.

Множинні залежності

Контролер може впроваджувати стільки залежностей, скільки потрібно для його роботи:

import { Controller, Get } from '@nestjs/common';
import { UsersService } from './users.service';
import { LoggerService } from '../common/logger.service';
import { ConfigService } from '@nestjs/config';

@Controller('users')
export class UsersController {
  constructor(
    private readonly usersService: UsersService,
    private readonly logger: LoggerService,
    private readonly config: ConfigService,
  ) {}

  @Get()
  findAll() {
    this.logger.log('Fetching all users');
    const pageSize = this.config.get<number>('PAGE_SIZE');
    return this.usersService.findAll(pageSize);
  }
}

Важливо зберігати кількість залежностей контролера помірною. Якщо контролер потребує п'яти або більше залежностей, це може сигналізувати про порушення принципу єдиної відповідальності — можливо, контролер виконує занадто багато функцій і його варто розділити.

Методи контролера: обробники маршрутів

Методи класу контролера, позначені декораторами маршрутів (такими як @Get(), @Post() тощо), називаються обробниками маршрутів (route handlers). Кожен обробник відповідає за обробку конкретного типу HTTP-запиту до певного URL.

Базова структура обробника

Обробник маршруту — це звичайний метод класу, який:

  1. Позначений декоратором HTTP-методу (@Get(), @Post(), @Put(), @Delete() тощо)
  2. Може приймати параметри (дані з запиту)
  3. Повертає дані, які автоматично серіалізуються у HTTP-відповідь
import { Controller, Get } from '@nestjs/common';

@Controller('products')
export class ProductsController {
  @Get()
  getAllProducts() {
    // Логіка отримання всіх продуктів
    return [
      { id: 1, name: 'Laptop', price: 1200 },
      { id: 2, name: 'Mouse', price: 25 },
    ];
  }
}

У цьому прикладі метод getAllProducts() обробляє GET-запити до /products. Повернутий масив об'єктів автоматично серіалізується у JSON та відправляється клієнту з HTTP-статусом 200 (OK).

Іменування методів контролера

NestJS не нав'язує жодних правил іменування методів контролера — можна називати їх як завгодно. Проте спільнота виробила певні конвенції, які підвищують читабельність коду:

RESTful конвенції (найпоширеніші):

  • findAll() або getAll(): отримання колекції ресурсів
  • findOne() або getOne(): отримання одного ресурсу за ідентифікатором
  • create(): створення нового ресурсу
  • update(): оновлення існуючого ресурсу
  • remove() або delete(): видалення ресурсу

CRUD-операції (явне іменування):

  • createUser(), getUser(), updateUser(), deleteUser()

Бізнес-орієнтовані назви (для складніших операцій):

  • activateAccount(), sendPasswordReset(), processPayment()
Вибирайте стиль іменування, який відповідає культурі вашої команди, та дотримуйтеся його узгоджено в усьому проєкті. Узгодженість важливіша за конкретний вибір стилю.

Повернення значень: автоматична серіалізація

Однією з найзручніших можливостей NestJS є автоматична серіалізація значень, що повертаються з обробників маршрутів. Розробнику не потрібно вручну викликати JSON.stringify() або налаштовувати заголовки Content-Type — фреймворк обробляє це автоматично.

Типи повертаємих значень

NestJS підтримує кілька типів значень, що повертаються:

Примітивні типи (string, number, boolean):

@Get('version')
getVersion(): string {
  return '1.0.0'; // Повертає рядок як plain text
}

@Get('count')
getCount(): number {
  return 42; // Повертає число у тілі відповіді
}

Об'єкти (найпоширеніший випадок):

@Get('user')
getUser(): object {
  return {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com',
  };
}
// HTTP Response: {"id":1,"name":"John Doe","email":"john@example.com"}
// Content-Type: application/json

Масиви об'єктів:

@Get('users')
getUsers(): object[] {
  return [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ];
}
// HTTP Response: [{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]

Екземпляри класів:

class User {
  constructor(
    public id: number,
    public name: string,
    public email: string,
  ) {}
}

@Get('user')
getUser(): User {
  return new User(1, 'John Doe', 'john@example.com');
}
// Серіалізується так само як об'єкт

Undefined та null:

@Get('nothing')
getNothing(): undefined {
  return undefined; // HTTP Response: 200 OK з порожнім тілом
}
Повернення undefined з обробника створює відповідь 200 OK з порожнім тілом, що може бути некоректним для деяких випадків. Якщо ресурс не знайдено, краще викинути виняток NotFoundException замість повернення undefined.

HTTP-статуси за замовчуванням

NestJS автоматично встановлює HTTP-статус залежно від типу запиту:

  • GET, PUT, PATCH, DELETE: статус 200 (OK)
  • POST: статус 201 (Created)

Це поведінка за замовчуванням, яку можна перевизначити через декоратор @HttpCode() (детальніше у наступних лекціях).

@Get('products')
getProducts() {
  return [{ id: 1, name: 'Product' }];
}
// HTTP/1.1 200 OK

Асинхронні обробники: Promise та async/await

Більшість реальних операцій у веб-застосунках є асинхронними: запити до бази даних, виклики зовнішніх API, читання файлів тощо. NestJS має вбудовану підтримку асинхронних обробників через механізм Promise та синтаксис async/await.

Повернення Promise

Обробник може повертати Promise, який розв'язується у значення. NestJS автоматично очікує розв'язання Promise перед відправленням відповіді:

import { Controller, Get } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get()
  findAll(): Promise<object[]> {
    // Імітація асинхронного запиту до бази даних
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve([
          { id: 1, name: 'Alice' },
          { id: 2, name: 'Bob' },
        ]);
      }, 100);
    });
  }
}

NestJS очікує, поки Promise розв'яжеться, і відправляє результат клієнту. Якщо Promise відхиляється (reject), фреймворк автоматично перетворює помилку на відповідну HTTP-відповідь з помилкою.

Синтаксис async/await

Більш елегантним та читабельним підходом є використання async/await:

import { Controller, Get } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  async findAll() {
    // await автоматично очікує розв'язання Promise
    const users = await this.usersService.findAll();
    return users;
  }
}

Ключове слово async перед методом перетворює його на асинхронну функцію, що завжди повертає Promise. Ключове слово await призупиняє виконання до розв'язання Promise, але не блокує інші запити — Node.js продовжує обробляти інші операції в цей час.

Обробка помилок в асинхронному коді

При роботі з асинхронними операціями важливо правильно обробляти помилки. NestJS автоматично перехоплює необроблені помилки та перетворює їх на HTTP-відповіді:

@Get(':id')
async findOne(id: string) {
  const user = await this.usersService.findById(id);
  
  if (!user) {
    // Викидання винятку автоматично перетворюється на HTTP 404
    throw new NotFoundException(`User with ID ${id} not found`);
  }
  
  return user;
}
Завжди використовуйте async/await замість .then()/.catch() у контролерах. Це робить код лінійним та читабельним, а помилки автоматично спливають догори та обробляються фреймворком.

Паралельне виконання асинхронних операцій

Якщо потрібно виконати кілька незалежних асинхронних операцій, використовуйте Promise.all() для паралельного виконання:

@Get('dashboard')
async getDashboard() {
  // Паралельне виконання трьох незалежних запитів
  const [users, products, orders] = await Promise.all([
    this.usersService.count(),
    this.productsService.count(),
    this.ordersService.count(),
  ]);

  return {
    totalUsers: users,
    totalProducts: products,
    totalOrders: orders,
  };
}

Це значно швидше, ніж послідовне виконання трьох await, оскільки запити виконуються одночасно.

Реєстрація контролера у модулі

Створення класу контролера — це лише половина справи. Щоб NestJS «знав» про існування контролера та зареєстрував його маршрути, контролер має бути оголошений у відповідному модулі через масив controllers у декораторі @Module().

Базова реєстрація

// users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  controllers: [UsersController], // Реєстрація контролера
  providers: [UsersService],      // Реєстрація сервісу
})
export class UsersModule {}

Коли модуль імпортується в кореневий AppModule, NestJS автоматично:

  1. Створює екземпляр контролера
  2. Розв'язує його залежності (впроваджує сервіси)
  3. Сканує декоратори маршрутів та реєструє їх у системі маршрутизації
  4. Готує контролер до обробки запитів

Множинні контролери в модулі

Один модуль може містити кілька контролерів, якщо вони логічно пов'язані:

@Module({
  controllers: [
    UsersController,
    UserProfileController,
    UserSettingsController,
  ],
  providers: [UsersService, ProfileService, SettingsService],
})
export class UsersModule {}

Проте якщо модуль стає занадто великим (більше 3-4 контролерів), це може бути сигналом, що його варто розділити на кілька менших модулів за принципом єдиної відповідальності.

Генерація контролера через NestJS CLI

NestJS CLI надає зручну команду для автоматичної генерації контролера з правильною структурою файлів та базовим кодом. Це значно прискорює розробку та забезпечує дотримання конвенцій фреймворку.

Базова команда генерації

nest generate controller <name>
# або скорочено:
nest g controller <name>

Наприклад, для створення контролера управління задачами:

bash
$ nest g controller tasks
CREATE src/tasks/tasks.controller.ts (99 bytes)
CREATE src/tasks/tasks.controller.spec.ts (485 bytes)
UPDATE src/app.module.ts (312 bytes)

CLI автоматично:

  1. Створює директорію tasks/ (якщо вона не існує)
  2. Генерує файл контролера tasks.controller.ts з базовою структурою
  3. Генерує файл тесту tasks.controller.spec.ts
  4. Оновлює найближчий модуль, додаючи контролер до масиву controllers

Згенерований код виглядає наступним чином:

// tasks.controller.ts
import { Controller } from '@nestjs/common';

@Controller('tasks')
export class TasksController {}

CLI вже встановив префікс маршруту (tasks) на основі назви контролера. Тепер розробник може додавати методи-обробники для конкретних операцій.

Опції генерації контролера

CLI надає кілька корисних опцій для налаштування процесу генерації:

--no-spec — пропускає створення тестового файлу:

nest g controller tasks --no-spec

Це корисно, якщо команда використовує іншу стратегію тестування або тести будуть додані пізніше.

--flat — створює файл у поточній директорії замість створення нової піддиректорії:

nest g controller tasks --flat
# Створює src/tasks.controller.ts замість src/tasks/tasks.controller.ts

--dry-run або -d — виконує команду в режимі симуляції без фактичного створення файлів:

nest g controller tasks --dry-run
# Показує, які файли будуть створені, але не створює їх

Це дозволяє перевірити результат перед фактичною генерацією.

Вказування модуля — можна явно вказати, до якого модуля має належати контролер:

nest g controller tasks --module=app
# або для вкладеного модуля:
nest g controller admin/users --module=admin
CLI автоматично визначає найближчий модуль для реєстрації контролера. Якщо контролер генерується всередині директорії існуючого модуля, він буде зареєстрований у цьому модулі. В іншому випадку CLI зареєструє контролер у кореневому AppModule.

Тонкий контролер: принцип мінімальної логіки

Один з найважливіших принципів проєктування контролерів у NestJS — це патерн тонкого контролера (Thin Controller Pattern). Контролер має бути максимально простим, виконуючи лише координаційні функції та делегуючи всю змістовну роботу сервісам.

Антипатерн: товстий контролер

Розглянемо приклад поганого проєктування, де контролер містить бізнес-логіку:

// ❌ Погана практика: бізнес-логіка в контролері
@Controller('orders')
export class OrdersController {
  constructor(
    private readonly database: DatabaseService,
    private readonly emailService: EmailService,
  ) {}

  @Post()
  async createOrder(orderData: any) {
    // Валідація даних в контролері
    if (!orderData.items || orderData.items.length === 0) {
      throw new BadRequestException('Order must contain items');
    }

    // Обчислення в контролері
    const total = orderData.items.reduce(
      (sum, item) => sum + item.price * item.quantity,
      0,
    );

    // Бізнес-правила в контролері
    const discount = total > 100 ? total * 0.1 : 0;
    const finalTotal = total - discount;

    // Прямий доступ до бази даних з контролера
    const order = await this.database.query(
      'INSERT INTO orders (total, discount) VALUES (?, ?)',
      [finalTotal, discount],
    );

    // Відправлення email з контролера
    await this.emailService.send({
      to: orderData.customerEmail,
      subject: 'Order confirmation',
      body: `Your order #${order.id} has been placed`,
    });

    return order;
  }
}

Проблеми цього підходу:

  1. Неможливість повторного використання: Логіку створення замовлення неможливо викликати з інших контекстів (консольні команди, обробники черг)
  2. Складність тестування: Тестування контролера вимагає моків для бази даних та email-сервісу
  3. Порушення єдиної відповідальності: Контролер «знає» про структуру бази даних, бізнес-правила та зовнішні сервіси
  4. Низька підтримуваність: Зміна бізнес-правил вимагає модифікації контролера

Правильний підхід: тонкий контролер

Той самий функціонал, реалізований правильно:

// ✅ Добра практика: тонкий контролер
@Controller('orders')
export class OrdersController {
  constructor(private readonly ordersService: OrdersService) {}

  @Post()
  async createOrder(createOrderDto: CreateOrderDto) {
    // Контролер лише координує виклик сервісу
    return this.ordersService.create(createOrderDto);
  }
}
// orders.service.ts - вся логіка в сервісі
@Injectable()
export class OrdersService {
  constructor(
    private readonly ordersRepository: OrdersRepository,
    private readonly emailService: EmailService,
  ) {}

  async create(createOrderDto: CreateOrderDto): Promise<Order> {
    // Валідація бізнес-правил
    this.validateOrderItems(createOrderDto.items);

    // Обчислення
    const total = this.calculateTotal(createOrderDto.items);
    const discount = this.calculateDiscount(total);

    // Збереження через репозиторій
    const order = await this.ordersRepository.create({
      ...createOrderDto,
      total: total - discount,
      discount,
    });

    // Відправлення повідомлення
    await this.emailService.sendOrderConfirmation(order);

    return order;
  }

  private validateOrderItems(items: OrderItem[]): void {
    if (!items || items.length === 0) {
      throw new BadRequestException('Order must contain items');
    }
  }

  private calculateTotal(items: OrderItem[]): number {
    return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }

  private calculateDiscount(total: number): number {
    return total > 100 ? total * 0.1 : 0;
  }
}

Переваги тонкого контролера:

  • Простота: Контролер зрозумілий з першого погляду
  • Повторне використання: Логіку можна викликати з будь-якого місця
  • Тестованість: Сервіс легко тестувати ізольовано
  • Підтримуваність: Зміни бізнес-логіки не торкаються контролера
Якщо метод контролера займає більше 5-7 рядків коду (не рахуючи сигнатуру та return), це ознака того, що частину логіки варто перенести в сервіс. Контролер має бути настільки простим, щоб його зрозуміти за кілька секунд.

Практичний приклад: контролер управління статтями

Для закріплення матеріалу розглянемо повноцінний приклад контролера для управління статтями блогу:

Цей контролер демонструє кілька ключових концепцій:

  1. Чітке розділення: Контролер координує, сервіс виконує
  2. Асинхронність: Всі методи асинхронні для узгодженості
  3. Множинні маршрути: Різні ендпоінти для різних варіантів фільтрації
  4. Повна реєстрація: Контролер та сервіс зареєстровані в модулі

Результуючі URL:

  • GET /articles — всі статті
  • GET /articles/published — опубліковані статті
  • GET /articles/featured — рекомендовані статті

Підсумок: контролер як координатор

Контролери є критичним компонентом архітектури NestJS, що формує HTTP API застосунку. Їхня роль полягає не у виконанні бізнес-логіки, а в координації взаємодії між HTTP-шаром та доменною логікою застосунку.

Ключові принципи роботи з контролерами:

  • Декоратор @Controller() визначає базовий префікс шляху та позначає клас як контролер
  • Методи-обробники позначені декораторами HTTP-методів координують обробку запитів
  • Автоматична серіалізація перетворює повернуті значення у JSON без додаткового коду
  • Асинхронність підтримується нативно через Promise та async/await
  • Реєстрація в модулі через масив controllers робить контролер видимим для фреймворку
  • Тонкий контролер делегує всю бізнес-логіку сервісам, залишаючись простим координатором

У наступних лекціях ми детально розглянемо декоратори HTTP-методів, роботу з параметрами маршрутів, query-параметрами, тілом запиту та іншими аспектами розробки REST API на NestJS.

✅ Що ми опанували

  • Роль контролера як координатора HTTP-комунікації
  • Використання декоратора @Controller для визначення префіксів
  • Структуру класу контролера та впровадження залежностей
  • Створення методів-обробників маршрутів
  • Автоматичну серіалізацію повертаємих значень
  • Роботу з асинхронним кодом через async/await
  • Реєстрацію контролера у модулі
  • Генерацію через CLI
  • Принцип тонкого контролера

🎯 Наступні кроки

У наступній лекції ми детально вивчимо декоратори маршрутизації HTTP-методів: @Get, @Post, @Put, @Patch, @Delete та інші. Ми навчимося визначати складні маршрути, використовувати wildcard-паттерни та обробляти різні типи HTTP-запитів.
Copyright © 2026