# NestJS面接:Guards、Interceptors、モジュラーアーキテクチャ > Guards、Interceptors、モジュラーアーキテクチャに関するNestJS技術面接の頻出質問を、具体的なTypeScriptコード例と技術解説とともに紹介します。 - Published: 2026-03-30 - Updated: 2026-05-04 - Author: Anthony Fillion-Maillet - Tags: nestjs, guards, interceptors, architecture, interview - Reading time: 9 min --- NestJSの技術面接では、フレームワークの3つの柱に一貫して焦点が当てられます。Guards、Interceptors、そしてモジュラーアーキテクチャです。これらのメカニズムは、よく構造化されたNestJSアプリケーションの背骨を形成し、採用担当者はフレームワークへの実際の習熟度を評価するために用います。 > **面接官が評価するポイント** > > Guardはアクセスを制御し(誰が入れるか)、Interceptorはデータを変換し(何が出入りするか)、モジュラーアーキテクチャはすべてを整理します。これら3つを使いこなすことは、基本的なCRUD操作を超えたNestJSへの深い理解を示します。 ## NestJSのGuardsの仕組みと面接の典型的なシナリオ Guardsは`CanActivate`インターフェースを実装し、リクエストがハンドラーに到達するかを判定します。ミドルウェアと異なり、GuardsはNestJSの実行コンテキスト(`ExecutionContext`)にアクセスでき、ハンドラーのメタデータに基づいた認可判断を可能にします。 定番の質問: 「ユーザーのロールを検証する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`は、カスタムデコレータによって付与されたメタデータを読み取ります。対応する`@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面接で問われます。 ## NestJSのInterceptors:リクエストとレスポンスの変換 Interceptorsは`NestInterceptor`を実装し、RxJSを使ってハンドラーの前後でデータストリームを操作します。その力は、実行パイプラインを表す`CallHandler`へのアクセスから生まれます。 よくある質問: 「実行時間を計測するロギング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`); }), ); } } ``` RxJSの`tap`オペレーターは、ストリームを変更せずに観測します。レスポンスを変換するには、適切なオペレーターは`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, })), ); } } ``` この標準化されたレスポンスパターンは面接で高評価につながります。API設計とレスポンスの一貫性を考える力を示せるからです。 > **Guard vs Interceptor vs Middleware** > > Middleware: 生のHTTP処理(Expressに似ています)。Guard: アクセスに関する真偽の判断。Interceptor: RxJSによるリクエスト/レスポンスストリームの変換。面接でこれら3つを混同するのは即座のレッドフラッグになります。 ## NestJSのモジュラーアーキテクチャ:スケーラブルなアプリケーションを構築する モジュラーアーキテクチャは、面接でジュニアとシニアの開発者を分けます。NestJSはモジュールベースの構成を強制しますが、本質的な質問はアーキテクチャ判断を狙います。いつモジュールを作成するか、モジュール間の依存関係をどう管理するかです。 上級の質問: 「適切に疎結合化されたモジュールでNestJSのEコマースアプリケーションをどう構造化しますか。」 ```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`が`PaymentsModule`をインポートし、`PaymentsModule`が`OrdersModule`をインポートすると、NestJSは起動時に失敗します。緊急脱出手段は`forwardRef`の使用です。 ```typescript // Resolving circular dependencies @Module({ imports: [forwardRef(() => PaymentsModule)], }) export class OrdersModule {} ``` 最も強い回答は、`forwardRef`は対症療法であって解決策ではないと説明します。正しい修正は、共有モジュール(`SharedOrderPaymentModule`)を抽出するリファクタリングか、Event Emitterを使って通信を疎結合化することです。 ## Dynamic Modulesと高度な設定 Dynamic Modulesは、プロジェクト間で再利用可能な設定可能なモジュールを作成します。このパターンはシニアレベルの面接で登場します。 質問: 「動的オプションを持つ設定可能なキャッシュモジュールを作成してください。」 ```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()`を介してオプションを注入します。 ```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`から設定を読み込み、本番環境で推奨されるプラクティスとなっています。 > **面接の落とし穴:@Global()** > > モジュールを`@Global()`としてマークするのは便利に見えますが、乱用すると見えない結合が生まれます。`@Global()`は横断的なサービス(config、cache、logger)に限定すべきだと面接で言及することは、アーキテクチャ的成熟度を示します。 ## カスタムデコレータとGuardsの合成 カスタムデコレータは、複数のGuardsとメタデータを単一のアノテーションに結合します。このトピックはミッドシニアのポジションで取り上げられます。 ```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), ); } ``` コントローラーでの使用例です。 ```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`パターンはコントローラーのコードをシンプルにし、認可ロジックを一元化します。面接でこのアプローチを能動的に提案することは強いシグナルになります。 ## まとめ - Guardsは`CanActivate`と`Reflector`を使い、ハンドラーのメタデータに基づくアクセス判断を行います - InterceptorsはRxJS(`tap`、`map`)を活用し、ハンドラーの前後でストリームを変換します - 実行順序Middleware → Guards → Interceptors → Pipes → Handlerはほぼ普遍的な質問です - 循環依存は`forwardRef`ではなく、アーキテクチャのリファクタリングで解決すべきです - Dynamic Modules(`forRoot`/`forRootAsync`)は再利用可能なコンポーネントを構築する力を示します - `applyDecorators`は複数のGuardsを読みやすい一つのデコレータに合成します - これらの質問を実コードで練習するなら、[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/ja/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture