# Співбесіда NestJS: Guards, Interceptors і модульна архітектура > Часті питання технічних співбесід з NestJS щодо Guards, Interceptors і модульної архітектури з конкретними прикладами коду TypeScript і технічними поясненнями. - Published: 2026-03-30 - Updated: 2026-05-04 - Author: Anthony Fillion-Maillet - Tags: nestjs, guards, interceptors, architecture, interview - Reading time: 9 min --- Технічні співбесіди з NestJS послідовно зосереджуються на трьох стовпах фреймворка: Guards, Interceptors і модульній архітектурі. Ці механізми утворюють кістяк будь-якого добре структурованого NestJS-додатка, і рекрутери використовують їх для оцінки реального володіння фреймворком. > **Що оцінюють співбесідники** > > Guard контролює доступ (хто заходить), Interceptor трансформує дані (що входить і виходить), а модульна архітектура все організовує. Опанування всіх трьох демонструє глибоке розуміння NestJS поза базовими CRUD-операціями. ## Як працюють Guards у NestJS і типові сценарії співбесід Guards реалізують інтерфейс `CanActivate` і визначають, чи досягне запит обробника. На відміну від middleware, Guards мають доступ до контексту виконання NestJS (`ExecutionContext`), що дозволяє ухвалювати рішення про авторизацію на основі метаданих обробника. Класичне питання: "Реалізуйте Guard, який перевіряє ролі користувача." ```typescript // roles.guard.ts import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { // Retrieve roles defined via the @Roles() decorator const requiredRoles = this.reflector.getAllAndOverride('roles', [ context.getHandler(), context.getClass(), ]); // No roles required means the route is public if (!requiredRoles) return true; // Extract user from the HTTP request const { user } = context.switchToHttp().getRequest(); // Check if the user holds at least one required role return requiredRoles.some((role) => user.roles?.includes(role)); } } ``` `Reflector` зчитує метадані, прикріплені користувацькими декораторами. Відповідний декоратор `@Roles()` має такий вигляд: ```typescript // roles.decorator.ts import { SetMetadata } from '@nestjs/common'; // Creates a decorator that attaches roles as metadata export const Roles = (...roles: string[]) => SetMetadata('roles', roles); ``` Сильна відповідь на співбесіді виходить за межі коду. Порядок виконання має значення: Middleware → Guards → Interceptors (до) → Pipes → Handler → Interceptors (після) → Exception Filters. Ця послідовність трапляється практично на кожній співбесіді з NestJS. ## Interceptors NestJS: трансформація запитів і відповідей Interceptors реалізують `NestInterceptor` і використовують RxJS для маніпулювання потоком даних до й після обробника. Їхня сила — у доступі до `CallHandler`, який представляє конвеєр виконання. Часте питання: "Створіть Interceptor для логування, який вимірює час виконання." ```typescript // logging.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class LoggingInterceptor implements NestInterceptor { private readonly logger = new Logger(LoggingInterceptor.name); intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); const { method, url } = request; const now = Date.now(); // next.handle() invokes the route handler return next.handle().pipe( tap(() => { // Runs AFTER the handler responds const duration = Date.now() - now; this.logger.log(`${method} ${url} — ${duration}ms`); }), ); } } ``` Оператор RxJS `tap` спостерігає за потоком, не змінюючи його. Для трансформації відповіді правильним оператором є `map`: ```typescript // transform.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; // Generic interface for a standardized API response interface ApiResponse { data: T; timestamp: string; path: string; } @Injectable() export class TransformInterceptor implements NestInterceptor> { intercept(context: ExecutionContext, next: CallHandler): Observable> { const request = context.switchToHttp().getRequest(); return next.handle().pipe( map((data) => ({ data, timestamp: new Date().toISOString(), path: request.url, })), ); } } ``` Цей патерн стандартизованої відповіді набирає очки на співбесідах: він демонструє вміння думати про дизайн API і узгодженість відповідей. > **Guard vs Interceptor vs Middleware** > > Middleware: сира HTTP-обробка (як Express). Guard: булеве рішення про доступ. Interceptor: трансформація потоку запит/відповідь за допомогою RxJS. Плутання цих трьох понять на співбесіді — миттєвий червоний прапорець. ## Модульна архітектура NestJS: побудова масштабованих застосунків Модульна архітектура відокремлює на співбесідах джуніорів від сеньйорів. NestJS вимагає організації на основі модулів, але справжні питання спрямовані на архітектурні рішення: коли створювати модуль і як керувати залежностями між модулями. Просунуте питання: "Як би ви структурували NestJS-застосунок електронної комерції з належно розв'язаними модулями?" ```typescript // orders/orders.module.ts import { Module } from '@nestjs/common'; import { OrdersService } from './orders.service'; import { OrdersController } from './orders.controller'; import { PaymentsModule } from '../payments/payments.module'; import { ProductsModule } from '../products/products.module'; @Module({ // Import modules that OrdersModule depends on imports: [PaymentsModule, ProductsModule], controllers: [OrdersController], providers: [OrdersService], // Expose OrdersService to modules that import OrdersModule exports: [OrdersService], }) export class OrdersModule {} ``` Класична пастка співбесіди: циклічні залежності. Якщо `OrdersModule` імпортує `PaymentsModule`, а `PaymentsModule` імпортує `OrdersModule`, NestJS падає під час старту. Аварійний вихід використовує `forwardRef`: ```typescript // Resolving circular dependencies @Module({ imports: [forwardRef(() => PaymentsModule)], }) export class OrdersModule {} ``` Найсильніша відповідь пояснює, що `forwardRef` — це обхідний шлях, а не виправлення. Належне виправлення полягає в рефакторингу для виділення спільного модуля (`SharedOrderPaymentModule`) або у використанні Event Emitter для розв'язання комунікацій. ## Dynamic Modules і просунута конфігурація Dynamic Modules створюють налаштовувані модулі, придатні для повторного використання між проєктами. Цей патерн з'являється на сеньйорських співбесідах. Питання: "Створіть налаштовуваний модуль кешу з динамічними опціями." ```typescript // cache/cache.module.ts import { Module, DynamicModule, Global } from '@nestjs/common'; import { CacheService } from './cache.service'; // Module configuration interface export interface CacheModuleOptions { ttl: number; // Time-to-live in seconds maxItems: number; // Maximum number of entries prefix: string; // Key prefix } @Global() // Available everywhere without explicit import @Module({}) export class CacheModule { static forRoot(options: CacheModuleOptions): DynamicModule { return { module: CacheModule, providers: [ { // Inject options via a custom token provide: 'CACHE_OPTIONS', useValue: options, }, CacheService, ], exports: [CacheService], }; } } ``` Відповідний сервіс впроваджує опції через `@Inject()`: ```typescript // cache/cache.service.ts import { Injectable, Inject } from '@nestjs/common'; import { CacheModuleOptions } from './cache.module'; @Injectable() export class CacheService { private store = new Map(); constructor(@Inject('CACHE_OPTIONS') private options: CacheModuleOptions) {} set(key: string, value: any): void { const prefixedKey = `${this.options.prefix}:${key}`; this.store.set(prefixedKey, { value, expiry: Date.now() + this.options.ttl * 1000, }); } get(key: string): T | null { const prefixedKey = `${this.options.prefix}:${key}`; const entry = this.store.get(prefixedKey); // Check expiration before returning if (!entry || entry.expiry < Date.now()) return null; return entry.value as T; } } ``` Патерн `forRoot` / `forRootAsync` — класика NestJS. `forRootAsync` завантажує конфігурацію з ін'єктовного `ConfigService`, що є рекомендованою практикою на продакшені. > **Пастка співбесіди: @Global()** > > Позначати модуль як `@Global()` здається зручним, але зловживання цим створює невидимі зв'язки. Згадати на співбесіді, що `@Global()` має бути зарезервованим для крізних сервісів (config, cache, logger), демонструє архітектурну зрілість. ## Користувацькі декоратори та композиція Guards Користувацькі декоратори об'єднують кілька Guards і метадані в одну анотацію. Ця тема виникає на позиціях mid-senior. ```typescript // auth.decorator.ts import { applyDecorators, UseGuards, SetMetadata } from '@nestjs/common'; import { JwtAuthGuard } from './jwt-auth.guard'; import { RolesGuard } from './roles.guard'; // Combines JWT authentication + role verification export function Auth(...roles: string[]) { return applyDecorators( SetMetadata('roles', roles), UseGuards(JwtAuthGuard, RolesGuard), ); } ``` Використання у контролері: ```typescript // orders/orders.controller.ts import { Controller, Get, Post, Body } from '@nestjs/common'; import { Auth } from '../auth/auth.decorator'; import { OrdersService } from './orders.service'; @Controller('orders') export class OrdersController { constructor(private ordersService: OrdersService) {} @Get() @Auth('admin', 'manager') // Single decorator replaces UseGuards + Roles findAll() { return this.ordersService.findAll(); } @Post() @Auth('admin') create(@Body() dto: CreateOrderDto) { return this.ordersService.create(dto); } } ``` Патерн `applyDecorators` спрощує код контролера й централізує логіку авторизації. Проактивно запропонувати цей підхід на співбесіді — сильний сигнал. ## Висновок - Guards використовують `CanActivate` і `Reflector` для рішень про доступ на основі метаданих обробника - Interceptors використовують RxJS (`tap`, `map`) для трансформації потоку до й після обробника - Порядок виконання Middleware → Guards → Interceptors → Pipes → Handler — майже універсальне питання - Циклічні залежності слід розв'язувати через архітектурний рефакторинг, а не через `forwardRef` - Dynamic Modules (`forRoot`/`forRootAsync`) демонструють здатність будувати багаторазові компоненти - `applyDecorators` поєднує кілька Guards в один читабельний декоратор - Практикуйте ці питання на реальному коді у [модулях NestJS](/technologies/node-nestjs/interview-questions/nestjs-modules-di) та [Interceptors](/technologies/node-nestjs/interview-questions/middleware-interceptors) --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture