NestJSのMiddlewareは、ルートハンドラ(Controller)に到達する前にリクエストを処理する仕組みです。ロギング、認証チェック、リクエストの変換など、複数のエンドポイントに共通する処理を一元化できます。本記事では、NestMiddlewareインターフェースを使用したクラスベースのMiddleware実装から、forRoutes()による適用範囲の柔軟な設定、そして実践的なロギングMiddlewareの構築までを詳しく解説します。

実行環境と前提条件

本記事の内容を実践するにあたり、以下の環境を前提としています。

項目 バージョン・要件
Node.js 20以上
npm 10以上
NestJS 11.x
OS Windows / macOS / Linux
エディタ VS Code(推奨)

事前に以下の準備を完了してください。

  • NestJS CLIのインストール済み
  • NestJSプロジェクトの作成済み

プロジェクトの作成方法はNestJS入門記事を参照してください。Module・Controller・Providerの基本はアーキテクチャ解説記事で確認できます。

Middlewareとは

Middlewareは、ルートハンドラが実行される前に呼び出される関数です。Expressのmiddlewareと同様に、リクエストオブジェクト(req)、レスポンスオブジェクト(res)、および次のmiddleware関数(next)にアクセスできます。

flowchart LR
    A[Client Request] --> B[Middleware 1]
    B --> C[Middleware 2]
    C --> D[Guard]
    D --> E[Interceptor]
    E --> F[Pipe]
    F --> G[Controller]
    G --> H[Response]
    
    style B fill:#4a9eff,color:#fff
    style C fill:#4a9eff,color:#fff

Middlewareで実行できる処理

Middlewareでは以下の処理を実行できます。

  • 任意のコードの実行
  • リクエストオブジェクトとレスポンスオブジェクトの変更
  • リクエスト・レスポンスサイクルの終了
  • スタック内の次のmiddleware関数の呼び出し

重要な点として、現在のmiddlewareがリクエスト・レスポンスサイクルを終了しない場合は、必ずnext()を呼び出して次のmiddlewareに制御を渡す必要があります。そうしないと、リクエストがハングした状態になります。

NestJSリクエストライフサイクルにおけるMiddlewareの位置

NestJSのリクエスト処理パイプラインでは、Middlewareは最初に実行されます。

実行順序 コンポーネント 主な役割
1 Middleware リクエスト前処理、ロギング、変換
2 Guard 認可チェック
3 Interceptor(前処理) リクエスト変換、キャッシュ
4 Pipe バリデーション、データ変換
5 Controller ビジネスロジック実行
6 Interceptor(後処理) レスポンス変換

クラスベースのMiddleware実装

NestJSでは、@Injectable()デコレータを持つクラスでNestMiddlewareインターフェースを実装することで、Middlewareを作成します。

基本的なLoggerMiddlewareの実装

まず、シンプルなロギングMiddlewareを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// src/common/middleware/logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const { method, originalUrl } = req;
    const timestamp = new Date().toISOString();
    
    console.log(`[${timestamp}] ${method} ${originalUrl}`);
    
    next();
  }
}

NestMiddlewareインターフェースはuseメソッドの実装を要求します。このメソッドは、Express(またはFastify)のmiddleware関数と同じシグネチャを持ちます。

Middlewareの適用

Middlewareを適用するには、モジュールクラスでNestModuleインターフェースを実装し、configure()メソッドを定義します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// src/app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { UsersModule } from './users/users.module';

@Module({
  imports: [UsersModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('users');
  }
}

この設定により、/usersで始まるすべてのルートにLoggerMiddlewareが適用されます。

forRoutes()による適用範囲の設定

MiddlewareConsumerforRoutes()メソッドは、Middlewareの適用範囲を柔軟に設定できます。

パスとHTTPメソッドによる制限

特定のHTTPメソッドにのみMiddlewareを適用できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';

@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'users', method: RequestMethod.GET });
  }
}

Controllerクラスによる指定

ルートパスの代わりにControllerクラスを指定することで、そのController内のすべてのルートにMiddlewareを適用できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { UsersController } from './users/users.controller';

@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(UsersController);
  }
}

ワイルドカードによるパターンマッチング

ルートパスにはワイルドカードを使用できます。NestJS 11以降では、名前付きワイルドカード(*splat)を使用します。

1
2
3
4
5
configure(consumer: MiddlewareConsumer) {
  consumer
    .apply(LoggerMiddleware)
    .forRoutes({ path: 'users/*splat', method: RequestMethod.ALL });
}

ワイルドカードをオプショナルにする場合は、波括弧で囲みます。

1
2
// 'users/' と 'users/123' の両方にマッチ
forRoutes({ path: 'users/{*splat}', method: RequestMethod.ALL });

複数のControllerへの適用

カンマ区切りで複数のControllerを指定できます。

1
2
3
4
5
configure(consumer: MiddlewareConsumer) {
  consumer
    .apply(LoggerMiddleware)
    .forRoutes(UsersController, ProductsController, OrdersController);
}

exclude()による除外設定

特定のルートをMiddleware適用対象から除外できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { UsersController } from './users/users.controller';

@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .exclude(
        { path: 'users/health', method: RequestMethod.GET },
        { path: 'users/metrics', method: RequestMethod.GET },
      )
      .forRoutes(UsersController);
  }
}

この設定では、UsersControllerのすべてのルートにLoggerMiddlewareが適用されますが、GET /users/healthGET /users/metricsは除外されます。

関数型Middleware

依存関係を必要としないシンプルなMiddlewareは、関数として定義することもできます。

1
2
3
4
5
6
7
// src/common/middleware/simple-logger.middleware.ts
import { Request, Response, NextFunction } from 'express';

export function simpleLogger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request... ${req.method} ${req.originalUrl}`);
  next();
}

関数型Middlewareの適用方法はクラスベースと同じです。

1
2
3
4
5
6
7
import { simpleLogger } from './common/middleware/simple-logger.middleware';

configure(consumer: MiddlewareConsumer) {
  consumer
    .apply(simpleLogger)
    .forRoutes('*');
}

依存性注入が不要な場合は、関数型Middlewareの使用が推奨されます。

複数のMiddlewareの適用

apply()メソッドには複数のMiddlewareをカンマ区切りで渡すことができます。Middlewareは指定した順序で実行されます。

1
2
3
4
5
6
7
8
9
import { corsMiddleware } from './common/middleware/cors.middleware';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { AuthMiddleware } from './common/middleware/auth.middleware';

configure(consumer: MiddlewareConsumer) {
  consumer
    .apply(corsMiddleware, LoggerMiddleware, AuthMiddleware)
    .forRoutes('*');
}

グローバルMiddlewareの登録

すべてのルートにMiddlewareを適用する場合、main.tsapp.use()を使用できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { simpleLogger } from './common/middleware/simple-logger.middleware';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // グローバルMiddlewareの登録
  app.use(simpleLogger);
  
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

グローバルMiddlewareで依存性注入を使用したい場合は、app.use()ではなくforRoutes('*')を使用してください。

1
2
3
4
5
configure(consumer: MiddlewareConsumer) {
  consumer
    .apply(LoggerMiddleware)
    .forRoutes('*');
}

依存性注入を活用したMiddleware

クラスベースのMiddlewareは、NestJSの依存性注入システムを完全にサポートしています。

ConfigServiceを利用したMiddleware

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// src/common/middleware/configurable-logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class ConfigurableLoggerMiddleware implements NestMiddleware {
  private readonly logLevel: string;

  constructor(private readonly configService: ConfigService) {
    this.logLevel = this.configService.get<string>('LOG_LEVEL', 'info');
  }

  use(req: Request, res: Response, next: NextFunction) {
    if (this.shouldLog()) {
      const { method, originalUrl, ip } = req;
      const userAgent = req.get('user-agent') || '';
      const timestamp = new Date().toISOString();
      
      console.log(
        `[${timestamp}] ${method} ${originalUrl} - ${ip} - ${userAgent}`
      );
    }
    
    next();
  }

  private shouldLog(): boolean {
    return this.logLevel === 'debug' || this.logLevel === 'info';
  }
}

カスタムLoggerサービスを注入したMiddleware

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// src/common/services/app-logger.service.ts
import { Injectable, LoggerService } from '@nestjs/common';

@Injectable()
export class AppLoggerService implements LoggerService {
  log(message: string) {
    console.log(`[LOG] ${new Date().toISOString()} - ${message}`);
  }

  error(message: string, trace?: string) {
    console.error(`[ERROR] ${new Date().toISOString()} - ${message}`);
    if (trace) {
      console.error(trace);
    }
  }

  warn(message: string) {
    console.warn(`[WARN] ${new Date().toISOString()} - ${message}`);
  }

  debug(message: string) {
    console.debug(`[DEBUG] ${new Date().toISOString()} - ${message}`);
  }

  verbose(message: string) {
    console.log(`[VERBOSE] ${new Date().toISOString()} - ${message}`);
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/common/middleware/advanced-logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { AppLoggerService } from '../services/app-logger.service';

@Injectable()
export class AdvancedLoggerMiddleware implements NestMiddleware {
  constructor(private readonly logger: AppLoggerService) {}

  use(req: Request, res: Response, next: NextFunction) {
    const { method, originalUrl, ip } = req;
    const startTime = Date.now();

    // レスポンス完了時のログ出力
    res.on('finish', () => {
      const duration = Date.now() - startTime;
      const { statusCode } = res;
      
      this.logger.log(
        `${method} ${originalUrl} ${statusCode} - ${duration}ms - ${ip}`
      );
    });

    next();
  }
}

実践的なMiddleware実装例

リクエストタイムログMiddleware

リクエストの処理時間を計測し、ログに出力するMiddlewareです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// src/common/middleware/request-time.middleware.ts
import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class RequestTimeMiddleware implements NestMiddleware {
  private readonly logger = new Logger('HTTP');

  use(req: Request, res: Response, next: NextFunction) {
    const { method, originalUrl } = req;
    const startTime = Date.now();

    res.on('finish', () => {
      const duration = Date.now() - startTime;
      const { statusCode } = res;
      const contentLength = res.get('content-length') || 0;
      
      const logMessage = `${method} ${originalUrl} ${statusCode} ${contentLength} - ${duration}ms`;
      
      if (statusCode >= 500) {
        this.logger.error(logMessage);
      } else if (statusCode >= 400) {
        this.logger.warn(logMessage);
      } else {
        this.logger.log(logMessage);
      }
    });

    next();
  }
}

リクエストIDMiddleware

各リクエストに一意のIDを付与し、ログのトレーサビリティを向上させます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// src/common/middleware/request-id.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { randomUUID } from 'crypto';

// Requestにカスタムプロパティを追加する型定義
declare global {
  namespace Express {
    interface Request {
      requestId?: string;
    }
  }
}

@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // ヘッダーから既存のリクエストIDを取得、なければ生成
    const requestId = req.get('X-Request-ID') || randomUUID();
    
    // リクエストオブジェクトにIDを付与
    req.requestId = requestId;
    
    // レスポンスヘッダーにもIDを設定
    res.set('X-Request-ID', requestId);
    
    next();
  }
}

CORSMiddleware

クロスオリジンリクエストを許可するMiddlewareの実装例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// src/common/middleware/cors.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class CorsMiddleware implements NestMiddleware {
  private readonly allowedOrigins: string[];

  constructor(private readonly configService: ConfigService) {
    const origins = this.configService.get<string>('CORS_ORIGINS', '*');
    this.allowedOrigins = origins.split(',').map((o) => o.trim());
  }

  use(req: Request, res: Response, next: NextFunction) {
    const origin = req.get('Origin');
    
    if (this.isOriginAllowed(origin)) {
      res.set('Access-Control-Allow-Origin', origin || '*');
    }
    
    res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
    res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Request-ID');
    res.set('Access-Control-Allow-Credentials', 'true');
    res.set('Access-Control-Max-Age', '86400');
    
    // プリフライトリクエストの処理
    if (req.method === 'OPTIONS') {
      res.status(204).end();
      return;
    }
    
    next();
  }

  private isOriginAllowed(origin: string | undefined): boolean {
    if (!origin) return true;
    if (this.allowedOrigins.includes('*')) return true;
    return this.allowedOrigins.includes(origin);
  }
}

実際のプロダクション環境では、NestJSの組み込みCORS機能(app.enableCors())を使用することが推奨されます。上記はMiddlewareの実装例として参考にしてください。

セキュリティヘッダーMiddleware

セキュリティ関連のHTTPヘッダーを設定するMiddlewareです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/common/middleware/security-headers.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class SecurityHeadersMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // XSSフィルターを有効化
    res.set('X-XSS-Protection', '1; mode=block');
    
    // MIMEタイプのスニッフィングを防止
    res.set('X-Content-Type-Options', 'nosniff');
    
    // クリックジャッキング対策
    res.set('X-Frame-Options', 'DENY');
    
    // Referrer情報の制御
    res.set('Referrer-Policy', 'strict-origin-when-cross-origin');
    
    // HTTPSの強制(本番環境のみ)
    if (process.env.NODE_ENV === 'production') {
      res.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
    }
    
    next();
  }
}

プロダクション環境では、helmetライブラリの使用も検討してください。

Middlewareモジュールの構成例

複数のMiddlewareを整理するためのモジュール構成例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/common/common.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { RequestIdMiddleware } from './middleware/request-id.middleware';
import { RequestTimeMiddleware } from './middleware/request-time.middleware';
import { SecurityHeadersMiddleware } from './middleware/security-headers.middleware';
import { AppLoggerService } from './services/app-logger.service';

@Module({
  imports: [ConfigModule],
  providers: [AppLoggerService],
  exports: [AppLoggerService],
})
export class CommonModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(
        RequestIdMiddleware,
        SecurityHeadersMiddleware,
        RequestTimeMiddleware,
      )
      .forRoutes('*');
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { CommonModule } from './common/common.module';
import { UsersModule } from './users/users.module';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    CommonModule,
    UsersModule,
  ],
})
export class AppModule {}

期待される動作の確認

Middlewareが正しく動作しているかを確認するため、アプリケーションを起動してリクエストを送信します。

1
2
# アプリケーションの起動
npm run start:dev

別のターミナルでリクエストを送信します。

1
2
# GETリクエストの送信
curl -i http://localhost:3000/users

期待される出力例(コンソール):

[2026-01-06T08:00:00.000Z] GET /users
[LOG] 2026-01-06T08:00:00.050Z - GET /users 200 - 50ms - ::1

レスポンスヘッダーの確認:

HTTP/1.1 200 OK
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
...

MiddlewareとInterceptor/Guardの使い分け

NestJSでは、リクエスト処理に関与する複数のコンポーネントがあります。適切なコンポーネントの選択が重要です。

コンポーネント 主な用途 実行コンテキスト DI
Middleware ロギング、ヘッダー操作、リクエスト変換 Express/Fastify
Guard 認可チェック、アクセス制御 NestJS ExecutionContext
Interceptor レスポンス変換、キャッシュ、実行時間計測 NestJS ExecutionContext

選択の指針

  • Middleware: HTTPレイヤーでの汎用的な処理(ロギング、CORSなど)
  • Guard: ルートへのアクセス可否の判定(認証・認可)
  • Interceptor: リクエスト/レスポンスの変換、横断的関心事

MiddlewareはExecutionContextにアクセスできないため、どのController/Handlerが実行されるかを知ることができません。ルートハンドラの情報が必要な場合は、GuardまたはInterceptorを使用してください。

まとめ

本記事では、NestJSのMiddlewareについて以下の内容を解説しました。

  • Middlewareの基本概念とリクエストライフサイクルにおける位置づけ
  • NestMiddlewareインターフェースを使用したクラスベースのMiddleware実装
  • forRoutes()exclude()による柔軟な適用範囲の設定
  • 関数型Middlewareの実装と使い分け
  • 依存性注入を活用した実践的なMiddleware(ロギング、リクエストID、セキュリティヘッダー)

Middlewareを適切に活用することで、Controllerのコードをシンプルに保ちながら、共通処理を効率的に実装できます。次回の記事では、Pipeによるバリデーションとデータ変換について解説します。

参考リンク