T-CREATOR

Zod × OpenAPI:`zod-to-openapi` で契約からドキュメントを自動生成

Zod × OpenAPI:`zod-to-openapi` で契約からドキュメントを自動生成

TypeScript でバリデーションを実装する際、Zod を使うとスキーマ定義から型を自動生成できて便利ですよね。しかし API 開発では、同じようなスキーマを OpenAPI ドキュメントにも書く必要があり、二重管理になってしまいます。

実は、zod-to-openapi というライブラリを使えば、Zod スキーマから OpenAPI ドキュメントを自動生成できるんです。これにより、バリデーションとドキュメントの一元管理が実現でき、保守性が大幅に向上します。

背景

API 開発における二重管理の課題

API 開発では、以下のような複数の定義を管理する必要があります。

  • TypeScript の型定義:静的型チェックのため
  • ランタイムバリデーション:実行時のデータ検証
  • API ドキュメント:Swagger/OpenAPI 仕様書

従来のアプローチでは、これらを別々に記述・管理していました。そのため、API 仕様を変更するたびに、複数箇所を修正する手間が発生していたのです。

以下の図は、従来の開発フローにおける課題を示しています。

mermaidflowchart TB
  dev["開発者"] -->|手動定義| types["TypeScript 型"]
  dev -->|手動定義| validation["バリデーションロジック"]
  dev -->|手動定義| openapi["OpenAPI 仕様書"]

  types -.->|不一致リスク| validation
  validation -.->|不一致リスク| openapi

  style types fill:#e1f5ff
  style validation fill:#fff4e1
  style openapi fill:#ffe1e1

上図のように、3 つの定義が独立しているため、変更時の同期漏れや不整合が発生しやすい状態でした。

Zod の登場と Single Source of Truth

Zod は、TypeScript ファースト なスキーマバリデーションライブラリです。スキーマ定義から型を自動推論できるため、型定義とバリデーションの一元化を実現しました。

しかし、OpenAPI ドキュメントは依然として手動管理が必要でした。ここで zod-to-openapi が登場し、Zod スキーマを OpenAPI 仕様に変換できるようになったのです。

以下の図は、zod-to-openapi を活用した理想的なフローを示しています。

mermaidflowchart LR
  zod["Zod スキーマ<br/>(Single Source)"] -->|自動推論| types["TypeScript 型"]
  zod -->|ランタイム| validation["バリデーション"]
  zod -->|zod-to-openapi| openapi["OpenAPI 仕様書"]

  openapi -->|自動生成| swagger["Swagger UI"]

  style zod fill:#b8e6b8
  style types fill:#e1f5ff
  style validation fill:#fff4e1
  style openapi fill:#ffe1e1
  style swagger fill:#f0e1ff

Zod スキーマを起点として、型・バリデーション・ドキュメントすべてを自動生成できる仕組みになります。

図で理解できる要点:

  • Zod スキーマが唯一の情報源(Single Source of Truth)となる
  • 型推論、バリデーション、OpenAPI 生成がすべて自動化される
  • 手動同期が不要になり、不整合リスクがゼロになる

課題

従来の開発における 3 つの問題

API 開発において、以下のような課題が顕在化していました。

1. 重複したスキーマ定義

TypeScript の interface、Zod スキーマ、OpenAPI の schemas を、それぞれ別々に定義する必要がありました。

typescript// TypeScript の型定義
interface User {
  id: string;
  name: string;
  email: string;
}
typescript// Zod スキーマ
const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
});
yaml# OpenAPI 定義(YAML)
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string
          format: email
      required:
        - id
        - name
        - email

上記のように、同じ内容を 3 回記述しなければならず、非効率でした。

2. 変更時の同期コスト

仕様変更が発生すると、3 箇所すべてを手動で修正する必要がありました。例えば、age フィールドを追加する場合、以下の作業が必要です。

#修正箇所作業内容
1TypeScript 型interface に age: number を追加
2Zod スキーマage: z.number() を追加
3OpenAPI 仕様schemas に age プロパティを追加
4テストコードage を含むテストデータを更新

このように、1 つの変更に対して複数ファイルの修正が必要で、修正漏れが発生しやすい状況でした。

3. ドキュメントの陳腐化

OpenAPI ドキュメントの更新を忘れると、実装とドキュメントが乖離してしまいます。

mermaidsequenceDiagram
  participant Dev as 開発者
  participant Code as 実装コード
  participant Doc as API ドキュメント
  participant User as API 利用者

  Dev->>Code: age フィールド追加
  Dev->>Doc: 更新を忘れる

  Note over Doc: 古い仕様のまま

  User->>Doc: 仕様を確認
  User->>Code: API リクエスト<br/>(age なし)
  Code-->>User: バリデーションエラー

  Note over User: ドキュメントと<br/>実装が不一致

このような不整合により、API 利用者が混乱し、問い合わせ対応のコストが増加していました。

図で理解できる要点:

  • ドキュメント更新漏れにより、利用者が誤った仕様でリクエストを送信
  • バリデーションエラーが発生し、開発体験が悪化
  • 手動同期に依存する限り、この問題は避けられない

解決策

zod-to-openapi による一元管理

zod-to-openapi を使用すると、Zod スキーマから OpenAPI 仕様を自動生成できます。これにより、以下のメリットが得られます。

#メリット説明
1Single Source of TruthZod スキーマのみを管理すればよい
2型安全性の向上TypeScript 型とバリデーションが自動で一致
3ドキュメントの常時最新化コード変更が即座にドキュメントへ反映
4開発速度の向上重複作業が削減され、本質的な開発に集中できる

以下は、全体のアーキテクチャを示した図です。

mermaidflowchart TB
  subgraph Source["情報源(Single Source)"]
    zod["Zod スキーマ定義"]
  end

  subgraph Auto["自動生成物"]
    types["TypeScript 型<br/>(z.infer)"]
    validation["ランタイム<br/>バリデーション"]
    openapi["OpenAPI 仕様<br/>(JSON)"]
  end

  subgraph Output["最終成果物"]
    swagger["Swagger UI"]
    client["型安全な<br/>API クライアント"]
  end

  zod -->|型推論| types
  zod -->|parse/safeParse| validation
  zod -->|zod-to-openapi| openapi

  openapi --> swagger
  types --> client

  style Source fill:#b8e6b8
  style Auto fill:#e1f5ff
  style Output fill:#fff4e1

図で理解できる要点:

  • Zod スキーマが唯一の定義元となり、すべてが自動生成される
  • 開発者は Zod スキーマの管理のみに集中できる
  • 型安全性とドキュメントの正確性が同時に保証される

インストールと基本設定

まずは必要なパッケージをインストールします。

bashyarn add zod @asteasolutions/zod-to-openapi
yarn add -D @types/node

次に、基本的な設定ファイルを作成しましょう。

typescript// src/openapi/registry.ts
import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';

// OpenAPI レジストリを作成
export const registry = new OpenAPIRegistry();

このレジストリに、後ほど Zod スキーマを登録していきます。

Zod スキーマの拡張

OpenAPI 仕様を生成するために、Zod スキーマに OpenAPI メタデータを追加します。

typescript// src/schemas/user.schema.ts
import { z } from 'zod';
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';

// Zod を OpenAPI 対応に拡張
extendZodWithOpenApi(z);

この初期化により、Zod スキーマに .openapi() メソッドが追加されます。

typescript// ユーザースキーマの定義
export const UserSchema = z
  .object({
    id: z.string().openapi({
      description: 'ユーザーの一意識別子',
      example: 'user_123',
    }),
    name: z.string().min(1).max(100).openapi({
      description: 'ユーザー名',
      example: '山田太郎',
    }),
    email: z.string().email().openapi({
      description: 'メールアドレス',
      example: 'yamada@example.com',
    }),
    age: z
      .number()
      .int()
      .min(0)
      .max(150)
      .optional()
      .openapi({
        description: '年齢(任意)',
        example: 25,
      }),
  })
  .openapi('User');

.openapi() メソッドで、OpenAPI ドキュメントに表示される説明文やサンプル値を指定できます。

typescript// TypeScript 型を自動推論
export type User = z.infer<typeof UserSchema>;

// 推論される型:
// type User = {
//   id: string;
//   name: string;
//   email: string;
//   age?: number | undefined;
// }

この時点で、型定義とバリデーションが Zod スキーマから自動生成されています。

エンドポイントの登録

API エンドポイントを定義し、レジストリに登録します。

typescript// src/openapi/routes.ts
import { registry } from './registry';
import { UserSchema } from '../schemas/user.schema';

// GET /users/{id} エンドポイントの定義
registry.registerPath({
  method: 'get',
  path: '/users/{id}',
  description: 'ユーザー情報を ID で取得します',
  summary: 'ユーザー取得',
  tags: ['Users'],
  request: {
    params: z.object({
      id: z.string().openapi({
        description: '取得するユーザーの ID',
        example: 'user_123',
      }),
    }),
  },
  responses: {
    200: {
      description: 'ユーザー情報の取得に成功',
      content: {
        'application/json': {
          schema: UserSchema,
        },
      },
    },
    404: {
      description: 'ユーザーが見つかりません',
      content: {
        'application/json': {
          schema: z.object({
            error: z.string().openapi({
              example: 'User not found',
            }),
          }),
        },
      },
    },
  },
});

上記のコードでは、パスパラメータ、レスポンススキーマ、エラーレスポンスをすべて Zod で定義しています。

typescript// POST /users エンドポイントの定義
registry.registerPath({
  method: 'post',
  path: '/users',
  description: '新しいユーザーを作成します',
  summary: 'ユーザー作成',
  tags: ['Users'],
  request: {
    body: {
      content: {
        'application/json': {
          schema: UserSchema.omit({ id: true }),
        },
      },
    },
  },
  responses: {
    201: {
      description: 'ユーザーの作成に成功',
      content: {
        'application/json': {
          schema: UserSchema,
        },
      },
    },
    400: {
      description: 'バリデーションエラー',
      content: {
        'application/json': {
          schema: z
            .object({
              error: z.string(),
              details: z.array(z.string()),
            })
            .openapi({
              example: {
                error: 'Validation failed',
                details: ['email: Invalid email format'],
              },
            }),
        },
      },
    },
  },
});

POST エンドポイントでは、UserSchema.omit({ id: true }) により、id を除いたスキーマをリクエストボディとして使用しています。

OpenAPI ドキュメントの生成

レジストリから OpenAPI 仕様を生成します。

typescript// src/openapi/generator.ts
import { OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi';
import { registry } from './registry';

// OpenAPI ジェネレーターを作成
const generator = new OpenApiGeneratorV3(
  registry.definitions
);
typescript// OpenAPI 仕様を生成
export const openApiDocument = generator.generateDocument({
  openapi: '3.0.0',
  info: {
    version: '1.0.0',
    title: 'User API',
    description:
      'Zod スキーマから自動生成された API ドキュメント',
  },
  servers: [
    {
      url: 'http://localhost:3000/api',
      description: '開発環境',
    },
    {
      url: 'https://api.example.com',
      description: '本番環境',
    },
  ],
});

この openApiDocument オブジェクトが、完全な OpenAPI 3.0 仕様書となります。

typescript// ドキュメントをファイルに出力
import fs from 'fs';
import path from 'path';

const outputPath = path.join(
  __dirname,
  '../../openapi.json'
);
fs.writeFileSync(
  outputPath,
  JSON.stringify(openApiDocument, null, 2)
);

console.log(
  `OpenAPI ドキュメントを生成しました: ${outputPath}`
);

これで、openapi.json ファイルが自動生成されます。

具体例

実践的な API 実装例

ここでは、Next.js API Routes で zod-to-openapi を活用した実装例を紹介します。

プロジェクト構成

plaintextsrc/
├── schemas/
│   ├── user.schema.ts       # ユーザースキーマ
│   └── product.schema.ts    # 商品スキーマ
├── openapi/
│   ├── registry.ts          # レジストリ設定
│   ├── routes.ts            # エンドポイント定義
│   └── generator.ts         # ドキュメント生成
├── pages/
│   └── api/
│       ├── users/
│       │   ├── [id].ts      # GET /api/users/:id
│       │   └── index.ts     # POST /api/users
│       └── docs.ts          # Swagger UI エンドポイント
└── utils/
    └── validate.ts          # バリデーションヘルパー

バリデーションヘルパーの作成

API ハンドラーで使い回せるバリデーションヘルパーを作成します。

typescript// src/utils/validate.ts
import { z } from 'zod';
import { NextApiRequest, NextApiResponse } from 'next';

export function validateRequest<T extends z.ZodTypeAny>(
  schema: T,
  data: unknown
): z.infer<T> {
  const result = schema.safeParse(data);

  if (!result.success) {
    throw new ValidationError(result.error);
  }

  return result.data;
}
typescript// カスタムエラークラス
export class ValidationError extends Error {
  constructor(public zodError: z.ZodError) {
    super('Validation failed');
    this.name = 'ValidationError';
  }

  toJSON() {
    return {
      error: this.message,
      details: this.zodError.errors.map((err) => ({
        path: err.path.join('.'),
        message: err.message,
      })),
    };
  }
}

このヘルパーにより、バリデーションエラーを統一的に処理できます。

API ハンドラーの実装

Zod スキーマを使った型安全な API ハンドラーを実装します。

typescript// src/pages/api/users/index.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { UserSchema } from '../../../schemas/user.schema';
import { validateRequest, ValidationError } from '../../../utils/validate';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  try {
    // リクエストボディをバリデーション
    const userData = validateRequest(
      UserSchema.omit({ id: true }),
      req.body
    );
typescript    // ユーザー作成処理(ダミー実装)
    const newUser = {
      id: `user_${Date.now()}`,
      ...userData,
    };

    // レスポンスもスキーマで検証
    const validatedUser = validateRequest(UserSchema, newUser);

    return res.status(201).json(validatedUser);
  } catch (error) {
typescript    // バリデーションエラーのハンドリング
    if (error instanceof ValidationError) {
      return res.status(400).json(error.toJSON());
    }

    // その他のエラー
    console.error('Unexpected error:', error);
    return res.status(500).json({
      error: 'Internal server error',
    });
  }
}

このように、リクエストとレスポンスの両方で型安全性を確保できます。

typescript// src/pages/api/users/[id].ts
import { NextApiRequest, NextApiResponse } from 'next';
import { UserSchema } from '../../../schemas/user.schema';
import { z } from 'zod';
import { validateRequest } from '../../../utils/validate';

// パスパラメータのスキーマ
const ParamsSchema = z.object({
  id: z.string(),
});

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'GET') {
    return res.status(405).json({ error: 'Method not allowed' });
  }
typescript  try {
    // パスパラメータをバリデーション
    const { id } = validateRequest(ParamsSchema, req.query);

    // ユーザー取得処理(ダミー実装)
    const user = {
      id,
      name: '山田太郎',
      email: 'yamada@example.com',
      age: 25,
    };

    // レスポンスをバリデーション
    const validatedUser = validateRequest(UserSchema, user);

    return res.status(200).json(validatedUser);
  } catch (error) {
typescript    if (error instanceof ValidationError) {
      return res.status(400).json(error.toJSON());
    }

    return res.status(500).json({
      error: 'Internal server error',
    });
  }
}

Swagger UI の組み込み

生成した OpenAPI ドキュメントを Swagger UI で表示します。

typescript// src/pages/api/docs.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { openApiDocument } from '../../openapi/generator';

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // OpenAPI JSON を返す
  res.status(200).json(openApiDocument);
}

次に、Swagger UI をセットアップします。

bashyarn add swagger-ui-react
yarn add -D @types/swagger-ui-react
typescript// src/pages/api-docs.tsx
import dynamic from 'next/dynamic';
import 'swagger-ui-react/swagger-ui.css';

// SSR を無効化して Swagger UI を読み込む
const SwaggerUI = dynamic(
  () => import('swagger-ui-react'),
  {
    ssr: false,
  }
);

export default function ApiDocs() {
  return (
    <div>
      <SwaggerUI url='/api/docs' />
    </div>
  );
}

これで、http:​/​​/​localhost:3000​/​api-docs にアクセスすると、自動生成された API ドキュメントが表示されます。

以下の図は、リクエストからレスポンスまでのフローを示しています。

mermaidsequenceDiagram
  participant Client as API クライアント
  participant Handler as API ハンドラー
  participant Validator as validateRequest
  participant Schema as Zod スキーマ
  participant DB as データベース

  Client->>Handler: POST /api/users<br/>{ name, email, age }
  Handler->>Validator: リクエストボディ検証
  Validator->>Schema: UserSchema.safeParse()

  alt バリデーション成功
    Schema-->>Validator: 検証済みデータ
    Validator-->>Handler: 型安全なデータ
    Handler->>DB: ユーザー作成
    DB-->>Handler: 作成されたユーザー
    Handler->>Validator: レスポンス検証
    Validator->>Schema: UserSchema.safeParse()
    Schema-->>Validator: 検証済みデータ
    Validator-->>Handler: 型安全なレスポンス
    Handler-->>Client: 201 Created<br/>{ id, name, email, age }
  else バリデーション失敗
    Schema-->>Validator: ZodError
    Validator-->>Handler: ValidationError
    Handler-->>Client: 400 Bad Request<br/>{ error, details }
  end

図で理解できる要点:

  • リクエストとレスポンスの両方でバリデーションを実施
  • バリデーション失敗時は統一されたエラー形式で返却
  • Zod スキーマが型安全性とランタイム安全性を同時に保証

複雑なスキーマの例

実務でよく使う、ネストしたスキーマや共通スキーマの定義例を紹介します。

typescript// src/schemas/common.schema.ts
import { z } from 'zod';
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';

extendZodWithOpenApi(z);

// ページネーション用の共通スキーマ
export const PaginationSchema = z
  .object({
    page: z.number().int().min(1).default(1).openapi({
      description: 'ページ番号',
      example: 1,
    }),
    limit: z
      .number()
      .int()
      .min(1)
      .max(100)
      .default(20)
      .openapi({
        description: '1 ページあたりの件数',
        example: 20,
      }),
    total: z.number().int().min(0).openapi({
      description: '全件数',
      example: 100,
    }),
  })
  .openapi('Pagination');
typescript// src/schemas/product.schema.ts
import { z } from 'zod';
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';

extendZodWithOpenApi(z);

// カテゴリスキーマ
export const CategorySchema = z
  .object({
    id: z.string().openapi({
      description: 'カテゴリ ID',
      example: 'cat_123',
    }),
    name: z.string().openapi({
      description: 'カテゴリ名',
      example: '電化製品',
    }),
  })
  .openapi('Category');
typescript// 商品スキーマ(ネスト構造)
export const ProductSchema = z
  .object({
    id: z.string().openapi({
      description: '商品 ID',
      example: 'prod_456',
    }),
    name: z.string().min(1).max(200).openapi({
      description: '商品名',
      example: 'ワイヤレスイヤホン',
    }),
    price: z.number().min(0).openapi({
      description: '価格(円)',
      example: 9800,
    }),
    category: CategorySchema.openapi({
      description: '商品カテゴリ',
    }),
    tags: z.array(z.string()).openapi({
      description: 'タグ一覧',
      example: [
        'Bluetooth',
        '防水',
        'ノイズキャンセリング',
      ],
    }),
    inStock: z.boolean().openapi({
      description: '在庫あり',
      example: true,
    }),
  })
  .openapi('Product');
typescript// ページネーション付き商品リストのレスポンススキーマ
export const ProductListResponseSchema = z
  .object({
    data: z.array(ProductSchema).openapi({
      description: '商品一覧',
    }),
    pagination: PaginationSchema.openapi({
      description: 'ページネーション情報',
    }),
  })
  .openapi('ProductListResponse');

このように、スキーマを組み合わせることで、複雑な API レスポンスも型安全に定義できます。

typescript// src/openapi/routes.ts(商品エンドポイント追加)
import { registry } from './registry';
import {
  ProductSchema,
  ProductListResponseSchema,
} from '../schemas/product.schema';

registry.registerPath({
  method: 'get',
  path: '/products',
  description: '商品一覧を取得します',
  summary: '商品一覧取得',
  tags: ['Products'],
  request: {
    query: z.object({
      page: z
        .string()
        .regex(/^\d+$/)
        .transform(Number)
        .optional(),
      limit: z
        .string()
        .regex(/^\d+$/)
        .transform(Number)
        .optional(),
      category: z.string().optional().openapi({
        description: 'カテゴリ ID でフィルタリング',
        example: 'cat_123',
      }),
    }),
  },
  responses: {
    200: {
      description: '商品一覧の取得に成功',
      content: {
        'application/json': {
          schema: ProductListResponseSchema,
        },
      },
    },
  },
});

クエリパラメータの型変換(transform)も含め、すべて Zod で定義しています。

CI/CD への組み込み

OpenAPI ドキュメントの自動生成をビルドプロセスに組み込む例です。

json// package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "yarn generate:openapi && next build",
    "generate:openapi": "ts-node src/openapi/generator.ts",
    "lint:openapi": "yarn generate:openapi && openapi-validator openapi.json"
  }
}
typescript// src/openapi/generator.ts(改良版)
import fs from 'fs';
import path from 'path';
import { OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi';
import { registry } from './registry';
import './routes'; // ルート定義を読み込む

const generator = new OpenApiGeneratorV3(
  registry.definitions
);

const openApiDocument = generator.generateDocument({
  openapi: '3.0.0',
  info: {
    version: process.env.npm_package_version || '1.0.0',
    title: 'User API',
    description:
      'Zod スキーマから自動生成された API ドキュメント',
  },
  servers: [
    {
      url: 'http://localhost:3000/api',
      description: '開発環境',
    },
    {
      url: 'https://api.example.com',
      description: '本番環境',
    },
  ],
});
typescript// ファイル出力
const outputPath = path.join(process.cwd(), 'openapi.json');
fs.writeFileSync(
  outputPath,
  JSON.stringify(openApiDocument, null, 2),
  'utf-8'
);

console.log(
  `✅ OpenAPI ドキュメントを生成しました: ${outputPath}`
);
yaml# .github/workflows/openapi.yml
name: OpenAPI Validation

on:
  pull_request:
    paths:
      - 'src/schemas/**'
      - 'src/openapi/**'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Generate OpenAPI document
        run: yarn generate:openapi

      - name: Validate OpenAPI schema
        run: yarn lint:openapi

      - name: Upload OpenAPI artifact
        uses: actions/upload-artifact@v3
        with:
          name: openapi-spec
          path: openapi.json

このワークフローにより、スキーマ変更時に自動で OpenAPI 仕様書が生成・検証されます。

まとめ

zod-to-openapi を活用することで、Zod スキーマから OpenAPI ドキュメントを自動生成でき、以下のメリットが得られます。

#メリット効果
1Single Source of TruthZod スキーマのみの管理で、型・バリデーション・ドキュメントすべてが自動生成される
2型安全性の向上TypeScript の型推論により、コンパイル時に型エラーを検出できる
3ドキュメントの常時最新化コード変更が即座にドキュメントへ反映され、不整合が発生しない
4開発速度の向上重複作業が削減され、本質的な機能開発に集中できる
5バリデーションの統一リクエスト・レスポンス両方で同じスキーマを使い、一貫性が保たれる

従来の開発では、型定義・バリデーション・ドキュメントを別々に管理する必要があり、変更時の同期コストが課題でした。zod-to-openapi により、この問題が根本的に解決されます。

特に、チーム開発や API の公開を行うプロジェクトでは、正確なドキュメントが開発体験を大きく左右します。Zod スキーマを起点とした自動生成により、常に最新のドキュメントを提供でき、API 利用者との信頼関係を構築できるでしょう。

ぜひ、次のプロジェクトで zod-to-openapi を導入してみてください。きっと、開発効率の向上を実感していただけるはずです。

関連リンク