Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке NestJS і які проблеми він вирішує?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**NestJS** - це Node.js фреймворк на базі TypeScript, який додає Angular-подібні модулі, декоратори та вбудоване впровадження залежностей (dependency injection) поверх Express. ```typescript @Controller('users') export class UsersController { constructor(private usersService: UsersService) {} @Get() getUsers() { return this.usersService.findAll(); } } ``` **Ключова ідея:** Express дає повну свободу без правил; NestJS задає структуру "один модуль на домен" щоб команда залишалась організованою з ростом проекту.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**NestJS** - це Node.js фреймворк на базі TypeScript, який додає Angular-подібну структуру (модулі, декоратори, впровадження залежностей) поверх Express для побудови масштабованих серверних додатків. ## Теорія ### TL;DR - NestJS схожий на перетворення розкиданих Express-файлів на пронумеровані пакети: кожна фіча отримує власний модуль (контролери + сервіси + провайдери), з'єднані автоматично - Головна різниця: Express не нав'язує жодних правил; NestJS задає чітку структуру через декоратори та модулі - Під капотом все одно запускається Express (або Fastify), тобто NestJS це шар зверху, не заміна - Правило вибору: прототип на тиждень на самоті? Express. Командний проект або API з 5+ ендпоінтів? NestJS ### Швидкий приклад Той самий GET /users ендпоінт в обох підходах: ```typescript // Express: працює, але куди це класти коли проект росте? const express = require('express'); const app = express(); app.get('/users', (req, res) => res.json([{ id: 1, name: 'Alice' }])); app.listen(3000); // NestJS: автоматично знаходиться через декоратори, одне чітке місце на фічу import { Controller, Get } from '@nestjs/common'; @Controller('users') export class UsersController { @Get() getUsers() { return [{ id: 1, name: 'Alice' }]; // GET /users → [{"id":1,"name":"Alice"}] } } // Запуск: nest start (модулі скануються автоматично) ``` Для трьох маршрутів Express цілком підходить. На тридцяти маршрутах з п'ятьма розробниками і без спільних домовленостей починається хаос. ### Ключова різниця Express дає порожній аркуш. Маршрути пишеш де хочеш, моделі імпортуєш вручну, правил немає. NestJS спочатку проводить лінії: кожна фіча живе в **модулі** (як NgModule в Angular), контролери обробляють HTTP через декоратори (`@Get`, `@Post`), а сервіси містять логіку з автоматично впровадженими залежностями (dependency injection). Це позбавляє від класичного командного питання "де взагалі живе цей маршрут?" на кожному code review в неструктурованих Express-проектах. ### Коли використовувати - Прототип на самоті, менше тижня: Express (немає кривої навчання, швидший старт) - Командний проект або API з 5+ ендпоінтів: NestJS (структура запобігає конфліктам ще до того як вони виникають) - Мікросервіси або WebSockets: NestJS (вбудована підтримка через `@nestjs/microservices` і `@nestjs/websockets`) - Висока навантаженість, 10k+ RPS: NestJS з Fastify-адаптером (приблизно вдвічі більша пропускна здатність порівняно зі стандартним Express) ### Порівняльна таблиця | Аспект | Express | NestJS | Fastify | Koa | |--------|---------|--------|---------|-----| | **TypeScript** | Опціонально | Вбудований | Опціонально | Опціонально | | **Структура** | Відсутня | Модулі задають розміщення | Відсутня | Мінімальна | | **DI-контейнер** | Вручну | Вбудований | Вручну | Вручну | | **Декоратори** | Ні | Так | Ні | Ні | | **CLI** | Ні | Так (`nest` CLI) | Ні | Ні | | **Крива навчання** | ~1 година | ~1 день | ~1 година | ~2 години | | **Найкраще для** | Скрипти, маленькі додатки | Командні API, великі бекенди | Швидкісні API | Легковагий middleware | ### Як NestJS запускається NestJS сканує `main.ts`, зчитує TypeScript-декоратори (`@Module`, `@Controller`) через пакет `reflect-metadata`, будує граф залежностей і створює провайдери в правильному порядку. Потім реєструє Express-обробники маршрутів з декорованих методів. Все це відбувається до першого запиту. ```typescript // main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // сканує декоратори, будує DI-граф await app.listen(3000); } bootstrap(); ``` Команди, які я бачив переходять з чистого Express на NestJS, кажуть одне і те ж після першого місяця: вони менше сперечаються про те куди класти новий код. ### Типові помилки **Звалити всі маршрути в app.controller.ts:** ```typescript // Неправильно: один контролер стає всім додатком @Controller() export class AppController { @Get('users') getUsers() { ... } @Get('products') getProducts() { ... } @Post('orders') createOrder() { ... } // через 500 рядків нічого не можна тестувати окремо } ``` Модулі існують саме щоб уникнути цього. Один контролер на один домен. `users.controller.ts` обробляє тільки users. **Ручне створення сервісу замість DI:** ```typescript // Неправильно constructor() { this.service = new UsersService(); // обходить DI-контейнер } // Правильно constructor(private service: UsersService) {} // впроваджується автоматично як singleton ``` Ручний `new` ламає автоматичне мокування в тестах і обходить singleton-scoping. Це найпоширеніша помилка NestJS на Stack Overflow. **Забути зареєструвати провайдер у модулі:** ```typescript // Неправильно: сервіс не вказаний у providers @Module({ controllers: [UsersController], // providers: [UsersService] <- відсутнє }) export class UsersModule {} // Результат: впровадження не спрацьовує, 404 у продакшені без чіткої помилки ``` NestJS ігнорує будь-який провайдер, якого немає у `providers`. Завжди реєструй там свої сервіси. ### Де NestJS використовується - Adidas: модулі для замовлень та інвентарю, GraphQL через `@nestjs/graphql` - Autodesk: мікросервіси з інтеграцією Kafka через `@nestjs/microservices` - Auth-потоки: `@nestjs/passport` + `@nestjs/jwt` є стандартною комбінацією в більшості NestJS-проектів - WebSocket-чати: `@nestjs/websockets` з Socket.io gateway ### Питання на співбесіді **Q:** Що відбувається при запуску NestJS? Що робить `NestFactory.create()`? **A:** Сканує `AppModule`, зчитує всі `@Module`-декоратори через `reflect-metadata`, будує граф залежностей і створює провайдери в порядку залежностей. Потім реєструє Express-обробники маршрутів. `app.listen(3000)` прив'язує HTTP-сервер останнім. **Q:** В чому різниця між провайдером і контролером? **A:** Контролери обробляють HTTP: отримують запити і повертають відповіді. Провайдери (з `@Injectable()`) містять бізнес-логіку і можуть впроваджуватись де завгодно. Контролер використовує провайдер; провайдер не повинен знати про HTTP. **Q:** Як переключитись з Express на Fastify? **A:** Передай `FastifyAdapter` у `NestFactory.create(AppModule, new FastifyAdapter())`. Всі декоратори залишаються точно такими самими. Fastify дає приблизно вдвічі більшу пропускну здатність при великому навантаженні. **Q:** Поясни порядок виконання guards, pipes та interceptors. **A:** Спочатку Guard (перевірка автентифікації). Потім Pipe (валідація та трансформація вхідних даних). Interceptor обгортає виклик контролера, спрацьовує до і після. Exception filter перехоплює все що кидає помилку. Цей порядок зафіксований у NestJS. **Q:** Як би ти побудував NestJS-бекенд для мільйона запитів на день? **A:** Окремі модулі на кожен домен, Redis-кешування через `@nestjs/cache-manager`, health checks через `@nestjs/terminus`, transient scope для сервісів зі станом і горизонтальне масштабування через Kubernetes з мікросервісами, що спілкуються через черги повідомлень. ## Приклади ### Базовий: модуль, контролер і сервіс ```typescript // users.module.ts import { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; @Module({ controllers: [UsersController], providers: [UsersService], // NestJS впроваджує це в контролер автоматично }) export class UsersModule {} // users.service.ts import { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { private users = [{ id: 1, name: 'Bob', email: 'bob@example.com' }]; findAll() { return this.users; } findOne(id: number) { return this.users.find(u => u.id === id); } } // users.controller.ts import { Controller, Get, Param } from '@nestjs/common'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private usersService: UsersService) {} // впроваджується автоматично @Get() getUsers() { return this.usersService.findAll(); } // GET /users → всі користувачі @Get(':id') getUser(@Param('id') id: string) { return this.usersService.findOne(+id); // GET /users/1 → один об'єкт користувача } } ``` `UsersService` вказаний у `providers`, тому NestJS створює один singleton-екземпляр і передає його в конструктор контролера. Без `new`, без ручного підключення. ### Середній: валідація вхідних даних через DTO і ValidationPipe ```typescript // create-user.dto.ts import { IsString, IsEmail, MinLength } from 'class-validator'; export class CreateUserDto { @IsString() @MinLength(2) name: string; @IsEmail() email: string; } // users.controller.ts (додано POST ендпоінт) import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; @Controller('users') export class UsersController { @Post() @UsePipes(new ValidationPipe()) createUser(@Body() dto: CreateUserDto) { // Невалідні дані не доходять до цього рядка return { message: `Created user ${dto.name}` }; } } // POST /users з { "name": "A", "email": "notanemail" } // → 400: ["name must be longer than or equal to 2 characters", "email must be an email"] ``` `ValidationPipe` зчитує class-validator декоратори з DTO і відхиляє невалідні дані до того як вони потрапляють у логіку контролера. Без жодних ручних перевірок. ### Просунутий: кругова залежність і forwardRef ```typescript // cats.service.ts import { Injectable, forwardRef, Inject } from '@nestjs/common'; import { DogsService } from '../dogs/dogs.service'; @Injectable() export class CatsService { constructor( @Inject(forwardRef(() => DogsService)) private dogsService: DogsService, ) {} meow() { return 'meow'; } } // dogs.service.ts import { Injectable, forwardRef, Inject } from '@nestjs/common'; import { CatsService } from '../cats/cats.service'; @Injectable() export class DogsService { constructor( @Inject(forwardRef(() => CatsService)) private catsService: CatsService, ) {} bark() { return this.catsService.meow() + ' woof'; } } // Без forwardRef з обох боків: // → Error: "Cannot resolve dependency CatsService" при запуску ``` Кругові залежності зазвичай сигналізують про проблему дизайну, яку варто виправити. Але коли двом сервісам справді потрібна взаємна залежність, `forwardRef` дозволяє NestJS вирішити її під час виконання замість падіння при старті. Фіксуй як технічний борг і плануй рефакторинг.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.