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

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

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

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

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

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

## Interceptorの使いどころ
ルートハンドラーの前後に実行されることを活かして…
![Interceptors_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/e95d84e0-8b1a-8168-5381-999c8814c09d.png)

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

https://docs.nestjs.com/interceptors

> 雛形作成コマンド
> ```
> nest g itc
> ```

## MiddleWareの使いどころ
リクエストが到達したら実行されることを活かして…
![Middlewares_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/af442a7c-b982-5ce3-6c74-3ba0ef726b07.png)

- (弊社では)アプリケーションのメンテナンス状態をDBにチェックしにいく
    - メンテナンス中であれば503を返す
- 他サードパーティのミドルウェアライブラリが使える
- [(テクニカル)パスが`/api`以外のものは全てフロントページのHTMLを返すなど](https://medium.com/@bo.vandersteene/use-nest-as-your-server-side-application-with-an-angular-frontend-540b0365bfa3)

https://docs.nestjs.com/middleware

> 雛形作成コマンド
> ```
> nest g mi
> ```

## Filterの使いどころ
NestJSにおいて、Filterといえば、`ExceptionFilter`です。
ルートハンドラーでの本名の処理を終え、インターセプターも通過した後なので例外のハンドリングに便利です。
![Filter_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/d81201b3-9b5c-0273-74ea-27fe7fe26dd0.png)


https://docs.nestjs.com/exception-filters

> 雛形作成コマンド
> ```
> nest g f
> ```

弊社では、エラーで返すボディを統一するために使っています。たとえばこんな感じ

```typescript
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ハンドラーに入ってくるデータ(パラメータなど)を変換するのに使います。
似たような概念が多く、私も間違った使い方をしているかもしれません、マサカリお待ちしてます。