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.

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.
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."
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 đọc metadata được gắn bởi các decorator tùy biến. Decorator @Roles() đi kèm có dạng như sau:
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."
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`);
}),
);
}
}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:
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,
})),
);
}
}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.
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?"
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:
// 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.
Sẵn sàng chinh phục phỏng vấn Node.js / NestJS?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
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."
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():
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;
}
}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.
Đá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.
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:
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
CanActivatevàReflectorcho 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 applyDecoratorsghé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 và Interceptors
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Thẻ
Chia sẻ
Bài viết liên quan

NestJS + Prisma: stack backend hiện đại cho Node.js
Hướng dẫn đầy đủ để xây dựng API backend hiện đại với NestJS và Prisma. Cấu hình, model, service, transaction và các best practice được giải thích chi tiết.

Cau Hoi Phong Van Backend Node.js: Huong Dan Day Du 2026
25 cau hoi phong van backend Node.js thuong gap nhat. Event loop, async/await, streams, clustering va hieu suat duoc giai thich chi tiet.

NestJS: Xây dựng REST API hoàn chỉnh từ đầu
Hướng dẫn đầy đủ xây dựng REST API chuyên nghiệp với NestJS. Controller, Service, Module, xác thực dữ liệu với class-validator và xử lý lỗi được giải thích chi tiết.