# Wawancara NestJS: Guards, Interceptors, dan arsitektur modular > Pertanyaan umum dalam wawancara teknis NestJS tentang Guards, Interceptors, dan arsitektur modular dengan contoh kode TypeScript konkret dan penjelasan teknis. - Published: 2026-03-30 - Updated: 2026-05-04 - Author: Anthony Fillion-Maillet - Tags: nestjs, guards, interceptors, architecture, interview - Reading time: 9 min --- Wawancara teknis NestJS secara konsisten berfokus pada tiga pilar framework: Guards, Interceptors, dan arsitektur modular. Mekanisme ini menjadi tulang punggung setiap aplikasi NestJS yang terstruktur dengan baik, dan perekrut menggunakannya untuk menilai penguasaan framework yang sesungguhnya. > **Apa yang dinilai pewawancara** > > Guard mengontrol akses (siapa yang masuk), Interceptor mengubah data (apa yang masuk dan keluar), sementara arsitektur modular mengatur semuanya. Menguasai ketiganya menunjukkan pemahaman NestJS yang mendalam, melampaui operasi CRUD dasar. ## Cara kerja Guards NestJS dan skenario wawancara umum Guards mengimplementasikan antarmuka `CanActivate` dan menentukan apakah sebuah request mencapai handler. Berbeda dengan middleware, Guards mengakses execution context NestJS (`ExecutionContext`), yang memungkinkan keputusan otorisasi berdasarkan metadata handler. Pertanyaan klasik: "Implementasikan Guard yang memeriksa peran pengguna." ```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` membaca metadata yang dilampirkan oleh decorator kustom. Decorator `@Roles()` yang sesuai terlihat seperti ini: ```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); ``` Jawaban yang kuat dalam wawancara melampaui kode. Urutan eksekusi penting: Middleware → Guards → Interceptors (sebelum) → Pipes → Handler → Interceptors (sesudah) → Exception Filters. Urutan ini muncul di hampir setiap wawancara NestJS. ## Interceptors NestJS: mengubah request dan response Interceptors mengimplementasikan `NestInterceptor` dan menggunakan RxJS untuk memanipulasi aliran data sebelum dan sesudah handler. Kekuatannya berasal dari akses ke `CallHandler`, yang merepresentasikan pipeline eksekusi. Pertanyaan yang sering muncul: "Buat sebuah Interceptor logging yang mengukur waktu eksekusi." ```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`); }), ); } } ``` Operator RxJS `tap` mengamati stream tanpa mengubahnya. Untuk mengubah response, operator yang tepat adalah `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, })), ); } } ``` Pola response standar ini memberi nilai bagus dalam wawancara: menunjukkan kemampuan berpikir tentang desain API dan konsistensi response. > **Guard vs Interceptor vs Middleware** > > Middleware: pemrosesan HTTP mentah (seperti Express). Guard: keputusan boolean untuk akses. Interceptor: transformasi aliran request/response dengan RxJS. Mencampurkan ketiga konsep ini dalam wawancara adalah red flag instan. ## Arsitektur modular NestJS: membangun aplikasi yang skalabel Arsitektur modular memisahkan developer junior dari senior dalam wawancara. NestJS memaksakan organisasi berbasis modul, tetapi pertanyaan sebenarnya menyasar keputusan arsitektur: kapan membuat modul dan bagaimana mengelola dependensi antar modul. Pertanyaan tingkat lanjut: "Bagaimana Anda menstruktur aplikasi e-commerce NestJS dengan modul yang dipisahkan dengan baik?" ```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 {} ``` Jebakan klasik wawancara: dependensi siklik. Jika `OrdersModule` mengimpor `PaymentsModule` dan `PaymentsModule` mengimpor `OrdersModule`, NestJS gagal saat startup. Jalan keluar daruratnya menggunakan `forwardRef`: ```typescript // Resolving circular dependencies @Module({ imports: [forwardRef(() => PaymentsModule)], }) export class OrdersModule {} ``` Jawaban paling kuat menjelaskan bahwa `forwardRef` adalah jalan pintas, bukan solusi sebenarnya. Perbaikan yang tepat adalah merefaktor untuk mengekstrak modul bersama (`SharedOrderPaymentModule`) atau menggunakan Event Emitter untuk memisahkan komunikasi. ## Dynamic Modules dan konfigurasi tingkat lanjut Dynamic Modules menciptakan modul yang dapat dikonfigurasi dan digunakan ulang antar proyek. Pola ini muncul dalam wawancara level senior. Pertanyaan: "Buat sebuah modul cache yang dapat dikonfigurasi dengan opsi dinamis." ```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 yang sesuai menyuntikkan opsi melalui `@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; } } ``` Pola `forRoot` / `forRootAsync` adalah klasik NestJS. `forRootAsync` memuat konfigurasi dari `ConfigService` yang dapat di-inject, yang menjadi praktik produksi yang direkomendasikan. > **Jebakan wawancara: @Global()** > > Menandai modul sebagai `@Global()` tampak praktis, tetapi penyalahgunaannya menciptakan kopling yang tidak terlihat. Menyebutkan dalam wawancara bahwa `@Global()` harus dicadangkan untuk service lintas-aplikasi (config, cache, logger) menunjukkan kematangan arsitektur. ## Custom Decorators dan komposisi Guards Custom decorators menggabungkan beberapa Guards dan metadata dalam satu anotasi tunggal. Topik ini muncul untuk posisi 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), ); } ``` Penggunaan dalam 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); } } ``` Pola `applyDecorators` menyederhanakan kode controller dan memusatkan logika otorisasi. Mengusulkan pendekatan ini secara proaktif dalam wawancara adalah sinyal yang kuat. ## Kesimpulan - Guards menggunakan `CanActivate` dan `Reflector` untuk keputusan akses berdasarkan metadata handler - Interceptors memanfaatkan RxJS (`tap`, `map`) untuk mengubah aliran sebelum dan sesudah handler - Urutan eksekusi Middleware → Guards → Interceptors → Pipes → Handler adalah pertanyaan yang hampir universal - Dependensi siklik harus diselesaikan melalui refactoring arsitektur, bukan dengan `forwardRef` - Dynamic Modules (`forRoot`/`forRootAsync`) menunjukkan kemampuan membangun komponen yang dapat digunakan kembali - `applyDecorators` menggabungkan beberapa Guards menjadi satu decorator yang mudah dibaca - Latihlah pertanyaan-pertanyaan ini dengan kode nyata pada [modul NestJS](/technologies/node-nestjs/interview-questions/nestjs-modules-di) dan [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/id/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture