Middlewareとは
Middlewareとは、ルートハンドラの前に呼び出される関数です。Middleware関数は、リクエストオブジェクトとレスポンスオブジェクト、そしてアプリケーションのリクエスト・レスポンスサイクルにおける
next()
Middleware関数にアクセスできます。NestJSにおけるMiddleware関数は一般的にnext
という変数で示されます。Middlewareの機能としては、以下のようなものがあります。 ・何らかのコードを実行できる・リクエストとレスポンスのオブジェクトを変更する・リクエスト・レスポンスサイクルを終了するStack内の次のMiddleware関数を呼び出す・現在のMiddleware関数がリクエストやレスポンスサイクルを終了しない場合は、next()
を呼び出して次のMiddleware関数に制御を移す必要があります。そうでなければ、リクエストは放置されたままになってしまいます。
カスタムネストMiddlewareは、関数または
@Injectable()
デコレータを持つクラスのどちらかで実装します。クラスは、NestMiddiewareインターフェイスを実装する必要があり、関数は特に必要ないです。または、クラスメソッドを使って簡単な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) {
console.log('Request...');
next();
}
}
NestJSのMiddlewareは、DIパターンを完全にサポートしています。ProviderやControllerと同様に、同じModule内で使えるDIパターンを実装できます。通常通り、これは
constructor
で実装されます。@Module()
デコレータには、Middlewareを記述する場所はありません。かわりに、Moduleクラスのconfigure()
メソッドで設定します。Middlewareを含むModuleは、NestModule
インターフェイスを実装する必要があります。AppModule
レベルでLoggerMiddleware
を設定しましょう。app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('cats');
}
}
上の例では、
CatsController
の内部で定義した/cats
ルートハンドラに対してLoggerMiddleware
を設定しました。Middlewareを設定するときに、ルートパスとリクエストメソッドを含むオブジェクトをforRoutes()
メソッドに渡すことで、さらに特定のリクエストメソッドにMiddlewareを限定できます。以下の例では、RequestMethod enum
をインポートしてリクエストメソッドの種類を指定しています。app.module.ts
import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: 'cats', method: RequestMethod.GET });
}
}
メモ
configure()
メソッドはasync/await
を使って非同期にできます。NestJSのMiddlewareではパターンベースのルートもサポートされています。例えば、
*
(アスタリスク)は任意の文字の組み合わせにマッチします。forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });ty
ab*cd
ルートパスは、abcd
、ab_cd
、abecd
などにマッチします。ルートパスには?
、+
、*
、()
という文字が使用でき、これらは正規表現に対応する文字のサブセットになっています。-
(ハイフン)と.
(ドット)は文字列ベースのパスでは文字通りに解釈されます。注意
fastify
パッケージは最新版のpath-to-regexp
パッケージを使用しており、*
(アスタリスク)をサポートしなくなりました。代わりに、パラメータを使う必要があります。MiddlewareConsumer
クラスはヘルパークラスです。Middlewareを管理するためのいくつかの組み込みメソッドを提供します。これらはすべて、流れるようなスタイルでシンプルに連鎖させることができます。forRoutes()
メソッドには、単一の文字列、複数の文字列やRouteInfo
オブジェクトやControllerクラスを渡すことができます。ほとんどの場合は、Controllerの一覧をコンマで区切って渡すことになるでしょう。以下のプログラムは、Controllerを一つだけ指定した場合の例です。
app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes(CatsController);
}
}
メモ
apply()
メソッドは、単一のMiddlewareを引数に取るか、複数のMiddlewareを引数にとって指定できます。特定のルートに対してMiddlewareを適用しない場合、
exclude()
メソッドを活用します。以下のように、除外するルートを特定する一つの文字列、複数の文字列またはRouteInfo
オブジェクトを渡すことができます。consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)',
)
.forRoutes(CatsController);
メモ
exclude()
メソッドは、path-to-regexp
パッケージを使ったパラメータをサポートしています。上記の例では、
exclude()
メソッドに渡された3つのルートを除き、CatsController
内で定義されたすべてのルートにLoggerMiddleware
がバインドされます。今回使用した
LoggerMiddleware
クラスは非常にシンプルです。メンバもなければ、追加のメソッドもなく、依存関係もありません。どうして、クラスではなく、単純な関数で定義できないのでしょうか?実はこれができます。このようなMiddlewareを関数型Middlewareと呼びます。ロガーMiddlewareをクラスベースから関数型Middlewareに変換してみましょう。
logger.middleware.ts
import { Request, Response, NextFunction } from 'express';
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
};
そして、これを
AppModule
内で使います。app.module.ts
consumer
.apply(logger)
.forRoutes(CatsController);
メモ
依存関係を必要としないMiddlewareを作成する場合は、よりシンプルな関数型Middlewareを使うことを検討してください。
前述のように、複数のMiddlewareを順次実行させるためには、
apply()
メソッド内にコンマ区切りでリストを記述すればよいです。consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
登録されたすべてのルートに対して、一度にMiddlewareをバインドしたい場合はINestApplicationのインスタンスが提供するuse()メソッドを利用できます。
main.ts
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
メモ
グローバルMiddlewareでDIコンテナにアクセスできません。
app.use()
を使用 する場合、代わりに関数型Middlewareを使用できます。また、クラスMiddlewareを使用し、AppModule
(または他のModule)内で.forRoutes('*')
を使って実装することもできます。Last modified 9mo ago