Співбесіда NestJS: Guards, Interceptors і модульна архітектура
Часті питання технічних співбесід з NestJS щодо Guards, Interceptors і модульної архітектури з конкретними прикладами коду TypeScript і технічними поясненнями.

Технічні співбесіди з NestJS послідовно зосереджуються на трьох стовпах фреймворка: Guards, Interceptors і модульній архітектурі. Ці механізми утворюють кістяк будь-якого добре структурованого NestJS-додатка, і рекрутери використовують їх для оцінки реального володіння фреймворком.
Guard контролює доступ (хто заходить), Interceptor трансформує дані (що входить і виходить), а модульна архітектура все організовує. Опанування всіх трьох демонструє глибоке розуміння NestJS поза базовими CRUD-операціями.
Як працюють Guards у NestJS і типові сценарії співбесід
Guards реалізують інтерфейс CanActivate і визначають, чи досягне запит обробника. На відміну від middleware, Guards мають доступ до контексту виконання NestJS (ExecutionContext), що дозволяє ухвалювати рішення про авторизацію на основі метаданих обробника.
Класичне питання: "Реалізуйте Guard, який перевіряє ролі користувача."
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<string[]>('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() має такий вигляд:
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 для логування, який вимірює час виконання."
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<any> {
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:
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<T> {
data: T;
timestamp: string;
path: string;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<ApiResponse<T>> {
const request = context.switchToHttp().getRequest();
return next.handle().pipe(
map((data) => ({
data,
timestamp: new Date().toISOString(),
path: request.url,
})),
);
}
}Цей патерн стандартизованої відповіді набирає очки на співбесідах: він демонструє вміння думати про дизайн API і узгодженість відповідей.
Middleware: сира HTTP-обробка (як Express). Guard: булеве рішення про доступ. Interceptor: трансформація потоку запит/відповідь за допомогою RxJS. Плутання цих трьох понять на співбесіді — миттєвий червоний прапорець.
Модульна архітектура NestJS: побудова масштабованих застосунків
Модульна архітектура відокремлює на співбесідах джуніорів від сеньйорів. NestJS вимагає організації на основі модулів, але справжні питання спрямовані на архітектурні рішення: коли створювати модуль і як керувати залежностями між модулями.
Просунуте питання: "Як би ви структурували NestJS-застосунок електронної комерції з належно розв'язаними модулями?"
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:
// Resolving circular dependencies
@Module({
imports: [forwardRef(() => PaymentsModule)],
})
export class OrdersModule {}Найсильніша відповідь пояснює, що forwardRef — це обхідний шлях, а не виправлення. Належне виправлення полягає в рефакторингу для виділення спільного модуля (SharedOrderPaymentModule) або у використанні Event Emitter для розв'язання комунікацій.
Готовий до співбесід з Node.js / NestJS?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Dynamic Modules і просунута конфігурація
Dynamic Modules створюють налаштовувані модулі, придатні для повторного використання між проєктами. Цей патерн з'являється на сеньйорських співбесідах.
Питання: "Створіть налаштовуваний модуль кешу з динамічними опціями."
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():
import { Injectable, Inject } from '@nestjs/common';
import { CacheModuleOptions } from './cache.module';
@Injectable()
export class CacheService {
private store = new Map<string, { value: any; expiry: number }>();
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<T>(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() має бути зарезервованим для крізних сервісів (config, cache, logger), демонструє архітектурну зрілість.
Користувацькі декоратори та композиція Guards
Користувацькі декоратори об'єднують кілька Guards і метадані в одну анотацію. Ця тема виникає на позиціях mid-senior.
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),
);
}Використання у контролері:
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 та Interceptors
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Теги
Поділитися
Пов'язані статті

NestJS + Prisma: сучасний бекенд-стек для Node.js
Повний посібник зі створення сучасного бекенд-API з NestJS і Prisma. Налаштування, моделі, сервіси, транзакції та найкращі практики.

Питання на співбесіді з Node.js Backend: Повний посібник 2026
25 найпоширеніших питань на співбесіді з Node.js backend. Event loop, async/await, потоки, кластеризація та продуктивність з детальними відповідями.

NestJS: Створення повноцінного REST API
Повний посібник зі створення професійного REST API з NestJS. Контролери, сервіси, модулі, валідація з class-validator та обробка помилок з практичними прикладами.