# Phỏng vấn NestJS: Guards, Interceptors và kiến trúc module > Các câu hỏi phổ biến trong phỏng vấn kỹ thuật NestJS về Guards, Interceptors và kiến trúc module với ví dụ mã TypeScript cụ thể và giải thích kỹ thuật. - Published: 2026-03-30 - Updated: 2026-05-04 - Author: Anthony Fillion-Maillet - Tags: nestjs, guards, interceptors, architecture, interview - Reading time: 9 min --- Các buổi phỏng vấn kỹ thuật NestJS thường tập trung vào ba trụ cột của framework: Guards, Interceptors và kiến trúc module. Những cơ chế này tạo nên xương sống của mọi ứng dụng NestJS được cấu trúc tốt, và nhà tuyển dụng sử dụng chúng để đánh giá mức độ thành thạo thực sự với framework. > **Điều mà người phỏng vấn đánh giá** > > Guard kiểm soát quyền truy cập (ai được vào), Interceptor biến đổi dữ liệu (cái gì vào và cái gì ra), còn kiến trúc module sắp xếp tất cả. Làm chủ cả ba thể hiện sự hiểu biết sâu sắc về NestJS vượt xa các thao tác CRUD cơ bản. ## Cách Guards của NestJS hoạt động và các kịch bản phỏng vấn phổ biến Guards triển khai interface `CanActivate` và quyết định liệu một request có đến được handler hay không. Khác với middleware, Guards có quyền truy cập vào execution context của NestJS (`ExecutionContext`), cho phép đưa ra quyết định ủy quyền dựa trên metadata của handler. Câu hỏi kinh điển: "Hãy triển khai một Guard kiểm tra vai trò người dùng." ```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` đọc metadata được gắn bởi các decorator tùy biến. Decorator `@Roles()` đi kèm có dạng như sau: ```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); ``` Một câu trả lời tốt trong phỏng vấn vượt xa phần code. Thứ tự thực thi rất quan trọng: Middleware → Guards → Interceptors (trước) → Pipes → Handler → Interceptors (sau) → Exception Filters. Trình tự này xuất hiện trong hầu hết mọi buổi phỏng vấn NestJS. ## Interceptors NestJS: biến đổi request và response Interceptors triển khai `NestInterceptor` và sử dụng RxJS để xử lý luồng dữ liệu trước và sau handler. Sức mạnh của chúng đến từ việc truy cập `CallHandler`, đại diện cho pipeline thực thi. Câu hỏi thường gặp: "Hãy tạo một Interceptor logging đo thời gian thực thi." ```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`); }), ); } } ``` Toán tử RxJS `tap` quan sát stream mà không thay đổi nó. Để biến đổi response, toán tử phù hợp là `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, })), ); } } ``` Mẫu response chuẩn hóa này được đánh giá cao trong phỏng vấn: nó cho thấy khả năng tư duy về thiết kế API và tính nhất quán của response. > **Guard vs Interceptor vs Middleware** > > Middleware: xử lý HTTP thô (giống Express). Guard: quyết định truy cập kiểu boolean. Interceptor: biến đổi luồng request/response bằng RxJS. Nhầm lẫn ba khái niệm này trong phỏng vấn là dấu hiệu cảnh báo ngay lập tức. ## Kiến trúc module NestJS: xây dựng ứng dụng có khả năng mở rộng Kiến trúc module phân tách lập trình viên junior với senior trong các buổi phỏng vấn. NestJS bắt buộc tổ chức theo module, nhưng các câu hỏi thực sự nhắm đến quyết định kiến trúc: khi nào tạo module và làm thế nào quản lý phụ thuộc giữa các module. Câu hỏi nâng cao: "Bạn sẽ cấu trúc một ứng dụng e-commerce NestJS với các module được tách biệt đúng cách như thế nào?" ```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 {} ``` Cái bẫy kinh điển trong phỏng vấn: phụ thuộc vòng tròn. Nếu `OrdersModule` import `PaymentsModule` và `PaymentsModule` import `OrdersModule`, NestJS sẽ thất bại khi khởi động. Lối thoát hiểm dùng `forwardRef`: ```typescript // Resolving circular dependencies @Module({ imports: [forwardRef(() => PaymentsModule)], }) export class OrdersModule {} ``` Câu trả lời mạnh nhất giải thích rằng `forwardRef` là một biện pháp tạm, không phải giải pháp. Cách khắc phục đúng là tái cấu trúc để tách ra một module dùng chung (`SharedOrderPaymentModule`) hoặc dùng Event Emitter để tách rời các giao tiếp. ## Dynamic Modules và cấu hình nâng cao Dynamic Modules tạo ra các module có thể cấu hình và tái sử dụng giữa các dự án. Mẫu này xuất hiện trong phỏng vấn cấp độ senior. Câu hỏi: "Hãy tạo một module cache có thể cấu hình với các tùy chọn động." ```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], }; } } ``` Service tương ứng inject các tùy chọn qua `@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; } } ``` Mẫu `forRoot` / `forRootAsync` là kinh điển của NestJS. `forRootAsync` tải cấu hình từ một `ConfigService` có thể inject, đây là phương pháp được khuyến nghị cho môi trường production. > **Cái bẫy phỏng vấn: @Global()** > > Đánh dấu một module là `@Global()` trông tiện lợi, nhưng lạm dụng nó tạo ra các sự ràng buộc vô hình. Nhắc đến trong phỏng vấn rằng `@Global()` nên được dành cho các service xuyên suốt (config, cache, logger) thể hiện sự trưởng thành về kiến trúc. ## Custom Decorators và việc kết hợp Guards Custom decorators kết hợp nhiều Guards và metadata vào một annotation duy nhất. Chủ đề này xuất hiện ở vị trí 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), ); } ``` Sử dụng trong controller: ```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); } } ``` Mẫu `applyDecorators` đơn giản hóa code controller và tập trung hóa logic ủy quyền. Chủ động đề xuất cách tiếp cận này trong phỏng vấn là một tín hiệu mạnh. ## Kết luận - Guards dùng `CanActivate` và `Reflector` cho các quyết định truy cập dựa trên metadata của handler - Interceptors tận dụng RxJS (`tap`, `map`) để biến đổi luồng trước và sau handler - Thứ tự thực thi Middleware → Guards → Interceptors → Pipes → Handler là câu hỏi gần như phổ quát - Phụ thuộc vòng tròn nên được xử lý bằng tái cấu trúc kiến trúc, không phải bằng `forwardRef` - Dynamic Modules (`forRoot`/`forRootAsync`) thể hiện khả năng xây dựng các thành phần tái sử dụng - `applyDecorators` ghép nhiều Guards thành một decorator dễ đọc - Hãy luyện tập các câu hỏi này với code thực tế trên [module NestJS](/technologies/node-nestjs/interview-questions/nestjs-modules-di) và [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/vi/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture