🐷

fastify-zod 推奨の register 関数を使ってみる

2023/07/10に公開

TL;DR

  • fastify-zod には zod モデルを取り込むための関数として register 関数が用意されている
  • register 関数を使うことで、@fastify/swagger や @fastify/swagger-ui を自分で読み込む必要がなくなる
  • fastify.zod.(delete|get|head|...) でルートを定義するとき、親インスタンスで指定した prefix を反映しないので注意

はじめに

いきなりですが、API ドキュメントは最新に保てていますか?最初ははりきって作成していますが、だんだんメンテナンスされなくなって実装との差分が大きくなり、更新のハードルが上がっていくという負のサイクルに陥りがちです。
fastify では、fastify-zod と fastify-swagger を使用することで zod で作成したバリデーションスキーマから OpenAPI 仕様を自動生成することができます。

下のクラメソさんのブログにお世話になった方も多いのではないでしょうか。私もその一人です。

さて、上の記事で紹介されている addSchema を使った実装ですが、fastify-zod の README を見ると legacy な実装として紹介されており、推奨は register を使った実装のようです。

試しに書いてみましょう。

実際に書き換えてみる

実装方法の確認のため下のリポジトリをフォークして register を使った実装に書き換えていきます。

ライブラリのバージョンアップ

まずはライブラリのバージョンアップをします。執筆時点での最新バージョンは以下の通りです。

  • fastify: 4.19.2
  • fastify-zod: 1.3.2
  • zod: 3.21.4
pnpm install fastify@latest fastify-zod@latest zod@latest

型定義を拡張する

register 関数内で zod を decorate しているため型定義を拡張してやります

app.ts
declare module "fastify" {
  interface FastifyInstance {
    readonly zod: FastifyZod<typeof productModels>;
  }
}

addSchema や swaggerUI の宣言を削除して register を追加する

@fastify/swagger や @fastify/swagger-ui の宣言は register 関数内で行ってくれるのでオプションは引数に渡してあげます。

app.ts
await register(server, {
  jsonSchemas: productSchemas,
  swaggerOptions: {
    openapi: {
      info: {
        title: "Sample API using Fastify and Zod.",
        description:
          "ZodのバリデーションスキーマからリッチなOpenAPI仕様を出力するサンプル",
        version: "1.0.0",
      },
    },
  },
  swaggerUiOptions: {
    routePrefix: "/docs",
    staticCSP: true,
  },
});

ルート定義を f.zod.get を利用したものに書き換える

fastify.register のオプションで指定した prefix は反映されないので自力でつけてあげます。
$ref が必要なくなってスッキリしましたね。

product.route.ts
const productRoutes = async (server: FastifyInstance) => {
  const { prefix } = server;
  server.zod.post(`${prefix}/`, {
    operationId: "createProduct",
    body: "createProductBodySchema",
    response: {
      201:{
        description: "登録完了",
        key: "productResponseSchema",
      }
    },
    tags: ["Product"],
  }, createProductHandler);

  server.zod.get(`${prefix}/:id`, {
    operationId: "getProduct",
    params: "getProductParamsSchema",
    querystring: "getProductQuerySchema",
    response: {
      200: {
        key: "productResponseSchema",
        description: "取得成功",
      },
    },
    tags: ["Product"],
  }, getProductHandler);
};

その他細かい修正をしていく

完全な差分はこちらを参照してください。

動作確認

サーバーを起動して localhost:3000/docs をブラウザで開いてみましょう。

pnpm dev

Example もちゃんと表示されていますね。

swagger-ui

register 関数はなにをしているのか

使い方を見てきたので、簡単に rgister 関数の処理も見ていきます。
主に4つの処理を行っています。

  • JSON スキーマの登録
  • @fastify/swagger の組み込み
  • @fastify/swagger-ui の組み込み
  • ルート定義を行う関数を zod として decorate

legacy な方法で実装者が行っていたことを代わりにやってくれていますね。

まとめ

fastify-zod で推奨されている register 関数を使用した記法に書き換えてみました。
prefix が使えないというのがトラップでしたが、記述量が減ってシンプルになりました。
fastify や zod を利用している場合は利用を検討してみてください。

Discussion