LoginSignup
7
0

More than 1 year has passed since last update.

NestJSのMiddlewareとinterceptorとFilterの使い分け

Last updated at Posted at 2022-08-17

NestJSでサーバーの処理を書いていて、似たような概念のMiddlewareinterceptorFilterの使い分けに悩んだことがありませんか?

今開発しているシステムで、ちょうどこの3つのNestJSに組み込まれている機能を使う場面があったので、実際に使ってみた感覚から使い分けについて記そうと思います。

実行順序について

もはやこれが結論でもあるのですが、これらには実行順序があります。

MiddleWare > Interceptor > Route Handler(本命の処理) > Interceptor > Filter

本命の処理であるRoute Handlerの前後にInterceptorは実行されます。

Interceptorの使いどころ

ルートハンドラーの前後に実行されることを活かして…
Interceptors_1.png

  • レスポンスのボディを加工する
    • たとえば、user[]{data: user[]}とするなど
    • ※ Interceptorではステータスコードの変更はできません
  • 純粋なルートごとの処理速度を計測したい

雛形作成コマンド

nest g itc

MiddleWareの使いどころ

リクエストが到達したら実行されることを活かして…
Middlewares_1.png

雛形作成コマンド

nest g mi

Filterの使いどころ

NestJSにおいて、Filterといえば、ExceptionFilterです。
ルートハンドラーでの本名の処理を終え、インターセプターも通過した後なので例外のハンドリングに便利です。
Filter_1.png

雛形作成コマンド

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ハンドラーに入ってくるデータ(パラメータなど)を変換するのに使います。

似たような概念が多く、私も間違った使い方をしているかもしれません、マサカリお待ちしてます。

7
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
0