NestJSでサーバーの処理を書いていて、似たような概念のMiddleware
とinterceptor
とFilter
の使い分けに悩んだことがありませんか?
今開発しているシステムで、ちょうどこの3つのNestJSに組み込まれている機能を使う場面があったので、実際に使ってみた感覚から使い分けについて記そうと思います。
実行順序について
もはやこれが結論でもあるのですが、これらには実行順序があります。
MiddleWare > Interceptor > Route Handler(本命の処理) > Interceptor > Filter
本命の処理であるRoute Handlerの前後にInterceptorは実行されます。
Interceptorの使いどころ
- レスポンスのボディを加工する
- たとえば、
user[]
を{data: user[]}
とするなど - ※ Interceptorではステータスコードの変更はできません
- たとえば、
- 純粋なルートごとの処理速度を計測したい
雛形作成コマンド
nest g itc
MiddleWareの使いどころ
- (弊社では)アプリケーションのメンテナンス状態をDBにチェックしにいく
- メンテナンス中であれば503を返す
- 他サードパーティのミドルウェアライブラリが使える
- (テクニカル)パスが
/api
以外のものは全てフロントページのHTMLを返すなど
雛形作成コマンド
nest g mi
Filterの使いどころ
NestJSにおいて、Filterといえば、ExceptionFilter
です。
ルートハンドラーでの本名の処理を終え、インターセプターも通過した後なので例外のハンドリングに便利です。
雛形作成コマンド
nest g f
弊社では、エラーで返すボディを統一するために使っています。たとえばこんな感じ
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'
import { HttpAdapterHost } from '@nestjs/core'
type ExceptionBodyBase = {
message: string
user_title?: string
user_message?: string
user_ref_url?: string
}
const errorBody: {
[key in
| HttpStatus.BAD_REQUEST
| HttpStatus.UNAUTHORIZED
| HttpStatus.NOT_FOUND
| HttpStatus.INTERNAL_SERVER_ERROR
| HttpStatus.GATEWAY_TIMEOUT]: ExceptionBodyBase
} = {
[HttpStatus.BAD_REQUEST]: {
message: 'Bad Request',
user_message: '要求の形式が正しくありません。',
},
[HttpStatus.UNAUTHORIZED]: {
message: 'Unauthorized',
user_message: '認証に失敗しました。アプリを再起動してください。',
},
[HttpStatus.NOT_FOUND]: {
message: 'Not Found',
user_message: 'リクエスト先が見つかりません。',
},
[HttpStatus.INTERNAL_SERVER_ERROR]: {
message: 'Internal Server Error',
user_message: 'エラーが発生しました。しばらく時間を置いてからお試しください。',
},
[HttpStatus.GATEWAY_TIMEOUT]: {
message: 'Gateway Timeout',
user_message: 'リクエストを処理できませんでした。再度お試しください。',
},
}
// Reference: https://docs.nestjs.com/exception-filters#catch-everything
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: HttpException, host: ArgumentsHost): void {
const { httpAdapter } = this.httpAdapterHost
const ctx = host.switchToHttp()
const httpStatus =
exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR
let body: ExceptionBodyBase
if (httpStatus === HttpStatus.SERVICE_UNAVAILABLE) {
// NOTE: 503はミドルウェアで動的にボディを生成するのでパススルーする
body = exception.getResponse() as ExceptionBodyBase
} else {
body = errorBody[httpStatus]
}
httpAdapter.reply(ctx.getResponse(), body, httpStatus)
}
}
ちなみにPipeはRouteハンドラーに入ってくるデータ(パラメータなど)を変換するのに使います。
似たような概念が多く、私も間違った使い方をしているかもしれません、マサカリお待ちしてます。