# สัมภาษณ์ 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 implement อินเทอร์เฟซ `CanActivate` และตัดสินว่าคำขอจะไปถึง handler หรือไม่ ต่างจาก middleware ตรงที่ Guards เข้าถึง execution context ของ NestJS (`ExecutionContext`) ซึ่งทำให้สามารถตัดสินใจเรื่องการอนุญาตโดยอิงตาม metadata ของ handler คำถามคลาสสิก: "จงเขียน 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` อ่าน metadata ที่ถูกแนบโดย custom decorator ส่วน decorator `@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: การแปลง request และ response Interceptors implement `NestInterceptor` และใช้ RxJS ในการจัดการสตรีมข้อมูลก่อนและหลัง handler ความสามารถหลักมาจากการเข้าถึง `CallHandler` ซึ่งเป็นตัวแทนของ pipeline การทำงาน คำถามที่พบบ่อย: "จงสร้าง logging 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`); }), ); } } ``` ตัวดำเนินการ `tap` ของ RxJS สังเกตสตรีมโดยไม่แก้ไข หากต้องการแปลง response ตัวดำเนินการที่เหมาะสมคือ `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, })), ); } } ``` รูปแบบ response ที่เป็นมาตรฐานนี้ได้คะแนนดีในการสัมภาษณ์: แสดงถึงความสามารถในการคิดเรื่องการออกแบบ API และความสอดคล้องของ response > **Guard vs Interceptor vs Middleware** > > Middleware: การประมวลผล HTTP ดิบ (เหมือน Express) Guard: การตัดสินใจเข้าถึงแบบ boolean Interceptor: การแปลงสตรีม request/response ด้วย RxJS การสับสนระหว่างสามแนวคิดนี้ในการสัมภาษณ์เป็นสัญญาณเตือนทันที ## สถาปัตยกรรมแบบโมดูลของ NestJS: สร้างแอปพลิเคชันที่ขยายขนาดได้ สถาปัตยกรรมแบบโมดูลแยกนักพัฒนาระดับ junior ออกจาก senior ในการสัมภาษณ์ NestJS บังคับการจัดระเบียบตามโมดูล แต่คำถามจริงพุ่งเป้าไปที่การตัดสินใจเชิงสถาปัตยกรรม: เมื่อไหร่ควรสร้างโมดูลและจัดการการพึ่งพาระหว่างโมดูลอย่างไร คำถามขั้นสูง: "คุณจะวางโครงสร้างแอปพลิเคชัน e-commerce ใน 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` import `PaymentsModule` และ `PaymentsModule` import `OrdersModule` NestJS จะล้มเหลวตอนเริ่มทำงาน ทางออกฉุกเฉินคือใช้ `forwardRef`: ```typescript // Resolving circular dependencies @Module({ imports: [forwardRef(() => PaymentsModule)], }) export class OrdersModule {} ``` คำตอบที่หนักแน่นที่สุดอธิบายว่า `forwardRef` เป็นการแก้ปัญหาเฉพาะหน้า ไม่ใช่การแก้ที่แท้จริง การแก้ไขที่ถูกต้องคือการ refactor เพื่อแยกโมดูลที่ใช้ร่วมกัน (`SharedOrderPaymentModule`) หรือใช้ Event Emitter เพื่อแยกการสื่อสารออกจากกัน ## Dynamic Modules และการกำหนดค่าขั้นสูง Dynamic Modules สร้างโมดูลที่กำหนดค่าได้และนำกลับมาใช้ใหม่ระหว่างโครงการ รูปแบบนี้ปรากฏในการสัมภาษณ์ระดับ senior คำถาม: "จงสร้างโมดูล cache ที่กำหนดค่าได้พร้อมตัวเลือกแบบไดนามิก" ```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 ตัวเลือกผ่าน `@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` ที่ inject ได้ ซึ่งเป็นแนวปฏิบัติที่แนะนำสำหรับ production > **กับดักการสัมภาษณ์: @Global()** > > การทำเครื่องหมายโมดูลเป็น `@Global()` ดูสะดวก แต่การใช้มากเกินไปสร้างการเชื่อมโยงที่มองไม่เห็น การกล่าวในการสัมภาษณ์ว่า `@Global()` ควรสงวนไว้สำหรับบริการที่ตัดข้าม (config, cache, logger) แสดงถึงวุฒิภาวะเชิงสถาปัตยกรรม ## Custom Decorators และการประกอบ Guards Custom decorators รวม Guards หลายตัวและ metadata ไว้ใน annotation เดียว หัวข้อนี้ปรากฏในตำแหน่ง 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), ); } ``` การใช้งานใน 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); } } ``` รูปแบบ `applyDecorators` ทำให้โค้ด controller เรียบง่ายขึ้นและรวมศูนย์ตรรกะการอนุญาต การเสนอแนวทางนี้อย่างเป็นเชิงรุกในการสัมภาษณ์เป็นสัญญาณที่หนักแน่น ## บทสรุป - Guards ใช้ `CanActivate` และ `Reflector` ในการตัดสินใจเข้าถึงตาม metadata ของ handler - Interceptors ใช้ประโยชน์จาก RxJS (`tap`, `map`) เพื่อแปลงสตรีมก่อนและหลัง handler - ลำดับการทำงาน Middleware → Guards → Interceptors → Pipes → Handler เป็นคำถามแทบจะสากล - การพึ่งพาแบบวงกลมควรแก้ไขด้วยการ refactor เชิงสถาปัตยกรรม ไม่ใช่ด้วย `forwardRef` - Dynamic Modules (`forRoot`/`forRootAsync`) แสดงถึงความสามารถในการสร้างคอมโพเนนต์ที่นำกลับมาใช้ใหม่ได้ - `applyDecorators` รวม Guards หลายตัวเข้าเป็น decorator เดียวที่อ่านง่าย - ฝึกคำถามเหล่านี้ด้วยโค้ดจริงใน [โมดูล 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/th/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture