🛴

Fastifyのミドルウェア上でカスタムのRequest/Reply型を定義する

2024/03/09に公開

はじめに

Node.js のサイドフレームワークとして有名なものとして、Express がありますが、最近までメンテナンスモードとなっていて、issue対応が滞っていたなどの事情があり積極的に採用するかは考慮の余地がありました。そういった経緯もあり、FastifyKoa , Hono など、様々な、Expressライクなサーバサイドフレームワークが注目されています。今回は、ツールチェインもある程度充実しており、Express との相性もいい Fastify のミドルウェア上でカスタムの Request/Reply 型を定義する方法を書いていこうと思います。

想定している読者

  • TypeScript がある程度書ける
  • Node.js を知っている
  • Node.js のサーバサイドフレームワークの選定に迷っている

Fastifyとは

Fastify とは、最小のインフラコストでロード時によりよいレスポンスを成し、ユーザーを幸せにするサーバフレームワークです。(若干意訳気味ですが..)

An efficient server implies a lower cost of the infrastructure, a better responsiveness under load...

この思想に基づいてか、最小構成でサーバをするためミドルウェアなどはデフォルトでは備わっておらず、ミドルウェアなどの設定は別に設定する必要があります。また、そのような構成になっていることで「ロード時によりよいレスポンスを成し」を実現しているのだと思います。

Fastifyでミドルウェアを作成する

少々複雑なため、Fastifyでミドルウェアを設定する方法についても下記に記していきます。

前提

Fastify でミドルウェアを設定する方法は主に2つあります。

  1. Fastify に対して Express のようにミドルウェアを設定できるようにするプラグインを導入する。
  2. Fastify 独自の形式でミドルウェアを設定する。

できるならパフォーマンスの観点で、2番目の選択肢を取る方がいいようなので今回は、2番目の方法について解説します。

You can also use middie, which provides support for simple Express-style middleware but with improved performance:
https://fastify.dev/docs/v3.29.x/Reference/Middleware/

実際に作ってみる

要件としては下記のものを作成します。

  • ミドルウェア上で取得したリクエストヘッダから取得した、
    ユーザーIDを他のリクエストにも伝搬させるミドルウェア。

下記のような定義で、アプリケーションサーバを立ち上げます。

index.ts
import fastify from 'fastify'
import fastifyMiddie from '@fastify/middie'
import { middleware } from middleware.ts

const app = fastify()
  .register(fastifyMiddie)
  .register(middleware)
  .get('/user', (request, reply) => {
    console.log(request.userId)
    ...
  })

下記に要件に沿った、ミドルウェアの設定方法を書いていきます。

middleware.ts
import fastifyPlugin from 'fastify-plugin'
import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'
import type { FastifyMiddieOptions } from '@fastify/middie'

const middlewareHandler = (request: FastifyRequest, reply: FastifyReply) => {
    const userId = request.header['userId']
    request.userId = userId
}

const middleware = fastifyPlugin((fastify: FastifyInstance, options: FastifyMiddieOptions, done: () => void) => {
  // decorateRequest()によって、Requestに含める変数を追加定義できます。
  // Replyも同様に、decoreateReply()があります。
  fastify.decorateRequest('userId', undefined)

  // preValidationのイベントフックは、Expressのミドルウェアと同等のタイミングで発火されます。
  fastify.addHook('preValidation', (request, reply, done) => {
    middlewareHandler(request, reply)
    // middlewareHandlerが非同期通信を含む場合は、Promise.prototype.finally()が呼び出されるタイミングでdone()を呼び出すといいです。
    done()
  })

  done()
})

export { middleware }

FastifyでRequest/Replyの型定義を伝播させる

前項で定義した middleware.ts では、FastifyRequest型 に userId というプロパティが存在しないという理由で、型エラーが生じてしまいます。そのため、Fastify のミドルウェア上で、新たに Request や Reply に新たに値を定義する際、同時に以下のような型定義をしてやる必要があります。

middleware.ts
declare module 'fastify' {
  interface FastifyRequest {
    userId:
      | string
      | undefined
  }
  interface FastifyReply {
    // Replyに追加する場合
  }
}

以上の方法をとることで Express に依存することもなく、型安全にミドルウェアを設定することができました。

おわりに

今回、Fastify の記事を書いてみました。Fastify の日本語の記事はまだまだ少なく、刺さる人に刺さればいいなあという気持ちで書いています。もし間違いや、こうした方がいいんじゃない?などありましたがご気軽にコメントしていだたけると幸いです。

Discussion