fastifyでハンドラに型付けする方法

2022.02.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

吉川@広島です。

Node.js+TypeScriptでfastifyを初めて使いました。ハンドラに型付けする方法を調べながら行ったので共有します。

環境

  • node 16.13.1
  • typescript 4.5.4
  • fastify 3.25.3

サンプルコード

以下のようなサンプルを用意しました。

import Fastify from 'fastify'

const fastify = Fastify({
  logger: true,
})

fastify.post('/ping/:id', (request, reply) => {
  console.log('q: ', request.query.q)
  console.log('myheader: ', request.headers.myheader)
  console.log('value: ', request.body.value)
  console.log('id: ', request.params.id)
  reply.type('application/json').code(200).send({ message: 'pong' })
})

fastify.listen(3000, '0.0.0.0', (err) => {
  if (err) throw err
})

/ping/:id

リクエスト情報に

  • クエリパラメータ
  • 独自ヘッダ
  • リクエストボディ
  • パスパラメータ

を要求するエンドポイントです。vscode-restclientを使って叩いてみます。

curl --request POST \
  --url 'http://localhost:3000/ping/1?q=queryvalue' \
  --header 'content-type: application/json' \
  --header 'myheader: myheader' \
  --header 'user-agent: vscode-restclient' \
  --data '{"value": "value"}'
# => {"message":"pong"}

サーバ側で以下のログが出力されます。

q:  queryvalue
myheader:  myheader
value:  value
id:  1

現状、これらのリクエスト情報は型から読み取ることができません。そのため、想定するパラメータがわかるようにこのハンドラを型付けしていきます。

そのまま型付け

ドキュメントのTypeScript#using-genericsが参考になります。 fastify.{get,post,put,delete} の型引数を渡すようにします。

import Fastify, {
  RouteHandlerMethod,
} from 'fastify'

const fastify = Fastify({
  logger: true,
})


fastify.post<{
  Querystring: {
    q: string
  }
  Headers: {
    myheader: string
  }
  Body: { value: string }
  Params: {
    id: string
  }
  Reply: {
    message: string
  }
}>('/ping/:id', (request, reply) => {
  console.log(request.query.q)
  console.log(request.headers.myheader)
  console.log(request.body.value)
  console.log(request.params.id)
  reply.type('application/json').code(200).send({ message: 'pong' })
})

fastify.listen(3000, '0.0.0.0', (err) => {
  if (err) throw err
})

型引数のパラメータの内容はそれぞれ以下のようです。

項目 説明
Querystring URLクエリパラメータ
Headers HTTPヘッダ
Body リクエストボディ (JSONやFormDataなど)
Params パスパラメータ
Reply レスポンスボデイ

ハンドラを名前つき関数に切り出して型付け

ここまでの例では .post メソッドに直接無名関数を渡していましたが、ファイル分割したいなどの動機でハンドラを名前つき関数に切り出したいことがよくあると思います。その場合は以下のようにします。

import Fastify, {
  RawReplyDefaultExpression,
  RawRequestDefaultExpression,
  RawServerDefault,
  RouteHandlerMethod,
} from 'fastify'

const fastify = Fastify({
  logger: true,
})

const handler: RouteHandlerMethod<
  RawServerDefault,
  RawRequestDefaultExpression,
  RawReplyDefaultExpression,
  {
    Querystring: {
      q: string
    }
    Headers: {
      myheader: string
    }
    Body: { value: string }
    Params: {
      id: string
    }
    Reply: {
      message: string
    }
  }
> = (request, reply) => {
  console.log('q: ', request.query.q)
  console.log('myheader: ', request.headers.myheader)
  console.log('value: ', request.body.value)
  console.log('id: ', request.params.id)
  reply.type('application/json').code(200).send({ message: 'pong' })
}
fastify.post('/ping', handler)

fastify.listen(3000, '0.0.0.0', (err) => {
  if (err) throw err
})

RouteHandlerMethod を使って型付けします。毎回RawServerDefault、RawRequestDefaultExpression、RawReplyDefaultExpressionの型引数を渡すのは大変なので、以下のような自作型を用意してあげると良いかも知れません。

import Fastify, {
  RawReplyDefaultExpression,
  RawRequestDefaultExpression,
  RawServerDefault,
  RouteHandlerMethod,
} from 'fastify'

const fastify = Fastify({
  logger: true,
})

// 自作型
export type MyRouteHandlerMethod<RouteGeneric> = RouteHandlerMethod<
  RawServerDefault,
  RawRequestDefaultExpression,
  RawReplyDefaultExpression,
  RouteGeneric
>

const handler: MyRouteHandlerMethod<
  {
    Querystring: {
      q: string
    }
    Headers: {
      myheader: string
    }
    Body: { value: string }
    Params: {
      id: string
    }
    Reply: {
      message: string
    }
  }
> = (request, reply) => {
  console.log('q: ', request.query.q)
  console.log('myheader: ', request.headers.myheader)
  console.log('value: ', request.body.value)
  console.log('id: ', request.params.id)
  reply.type('application/json').code(200).send({ message: 'pong' })
}
fastify.post('/ping', handler)

fastify.listen(3000, '0.0.0.0', (err) => {
  if (err) throw err
})