Nuxt3 ベストプラクティス03: OpenAPI ドキュメント出力

公開日 2025年2月2日
更新日 2025年2月2日
14 分で読める
Vue

1. はじめに

Nuxt 3 では、Nitro サーバーの組み込み OpenAPI ドキュメント生成機能を活用し、Swagger UI や Scalar UI を用いて視覚的に表示できます。しかし、この機能には以下の問題があります。

  1. 実験的機能: Nitro の OpenAPI サポートはまだ実験段階であり、将来的に変更または削除される可能性があります。
  2. API フィルタリングの欠如: すべての Server API が公開され、特定の API を除外する設定がありません。
  3. Zod との統合不可: zod を使用してスキーマを定義した場合、Nitro は OpenAPI 形式へ自動変換できず、別途 JSON Schema を定義する必要があります。

そのため、より柔軟なソリューションを採用することを推奨します。本記事で紹介する方法には以下のメリットがあります。

  • zod とシームレスに統合し、zod スキーマから OpenAPI ドキュメントを直接生成可能。
  • API フィルタリングに対応し、特定の API を非公開にできる。

2. 実装方法

2.1 依存関係のインストール

まず、zod とその OpenAPI 互換ライブラリ zod-openapi をインストールします。

npm install zod zod-openapi

2.2 方法の概要

主な手順は以下のとおりです。

  1. OpenAPI レジストリを作成: API の定義を管理。
  2. defineOpenAPI メソッドの提供: 各 API がこのメソッドを通じてレジストリに登録。
  3. OpenAPI 生成 API を定義: レジストリのデータを取得し、OpenAPI JSON ドキュメントを生成。

2.3 OpenAPI レジストリの作成

server/utils ディレクトリに openapi-registry.ts を作成し、API 定義を管理します。

// openapi-registry.ts
import z from 'zod';
import { extendZodWithOpenApi } from 'zod-openapi';

extendZodWithOpenApi(z);

export type OpenAPIEndpoint = {
  /** API のタグ */
  tags: string[];
  /** API のパス (例: '/api/users') */
  path: string;
  /** HTTP メソッド (例: 'get', 'post') */
  method: string;
  /** 操作 ID */
  operationId?: string;
  /** API の概要 */
  summary?: string;
  /** API の詳細説明 */
  description?: string;
  /** クエリパラメータのスキーマ */
  querySchema?: z.ZodObject<any, any>;
  /** パスパラメータのスキーマ */
  pathParamsSchema?: z.ZodObject<any, any>;
  /** リクエストボディのスキーマ */
  bodySchema?: z.ZodObject<any, any>;
  /** レスポンスボディのスキーマ */
  responseSchema?: z.ZodType<any, any>;
};

// API 定義を格納するレジストリ
const registry: OpenAPIEndpoint[] = [];

/**
 * OpenAPI エンドポイントを登録
 * @param data OpenAPI エンドポイントデータ
 */
export function defineOpenAPI(data: OpenAPIEndpoint) {
  registry.push(data);
}

/**
 * 登録された API の一覧を取得
 */
export function getOpenAPIRegistry() {
  return registry;
}

2.4 Server API で OpenAPI 情報を定義

server/api/student.post.ts に API を定義し、OpenAPI 情報を登録します。

// server/api/student.post.ts
import { defineEventHandler, readValidatedBody } from 'h3';
import z from 'zod';

const bodySchema = z.object({
  name: z.string().min(1).max(100).describe('学生の名前'),
  address: z.string().min(10).max(100).describe('学生の住所'),
});

const responseSchema = z.object({
  message: z.string(),
});

defineOpenAPI({
  tags: ['学生'],
  path: '/api/student',
  method: 'post',
  operationId: 'student-add',
  summary: '学生の追加',
  description: '学生情報を追加する',
  bodySchema,
  responseSchema,
});

export default defineEventHandler(async (event) => {
  const params = await readValidatedBody(event, bodySchema.parse);
  console.log(params);
  return { message: 'success' };
});

2.5 OpenAPI JSON ドキュメントを生成する

server/api/openapi.json.dev.ts を作成して、OpenAPI JSON ドキュメントを生成する。

// openapi.json.dev.ts
import { defineEventHandler } from 'h3';
import { ZodOptional } from 'zod';
import { createSchema } from 'zod-openapi';

export default defineEventHandler(() => {
  const endpoints = getOpenAPIRegistry();
  const paths: Record<string, any> = {};

  endpoints.forEach((ep) => {
    if (!paths[ep.path]) {
      paths[ep.path] = {};
    }
    const method = ep.method.toLowerCase();

    // Query String Parameters
    const queryParameters = ep.querySchema
      ? Object.entries(ep.querySchema.shape).map(([name, schema]) => {
          return {
            name,
            in: 'query',
            required: !(schema instanceof ZodOptional),
            ...createSchema(schema as any),
          };
        })
      : [];

    // Path Parameters
    const pathParameters = ep.pathParamsSchema
      ? Object.entries(ep.pathParamsSchema.shape).map(([name, schema]) => {
          return {
            name,
            in: 'path',
            required: true,
            ...createSchema(schema as any),
          };
        })
      : [];

    const parameters = [...pathParameters, ...queryParameters];

    // Request Body
    const requestBody = ep.bodySchema
      ? {
          content: {
            'application/json': createSchema(ep.bodySchema),
          },
        }
      : undefined;

    // Response
    const responses = ep.responseSchema
      ? {
          200: {
            description: '成功',
            content: {
              'application/json': createSchema(ep.responseSchema),
            },
          },
        }
      : {
          200: { description: '成功' },
        };

    paths[ep.path][method] = {
      tags: ep.tags,
      operationId: ep.operationId || `${method}_${ep.path}`,
      summary: ep.summary,
      description: ep.description,
      parameters,
      ...(requestBody ? { requestBody } : {}),
      responses,
    };
  });

  // OpenAPIの基本情報を設定する
  const openApiDoc = {
    openapi: '3.0.0',
    info: {
      title: 'My API test',
      version: '1.0.0',
      description: 'This is a test API',
    },
    servers: [
      {
        url: 'http://localhost:3000',
      },
    ],
    tags: [
      {
        name: '学生',
        description: '学生に関するAPI',
      },
    ],
    paths,
  };

  return openApiDoc;
});

注意:Nitro では、ファイル名が .dev.ts である API がproduction環境に含まれません。

2.6 OpenAPI ドキュメントの取得

設定完了後、Nuxt サーバーを起動し、以下の URL にアクセスすると OpenAPI ドキュメントを取得できます。

http://localhost:<port>/api/openapi.json

この JSON ファイルを Swagger UI や他の OpenAPI ツールと組み合わせることで、API ドキュメントを視覚化できます。

3. まとめ

本記事では、zod-openapi を活用し、defineOpenAPI 機構を導入することで、自動 API ドキュメント生成 を実現しました。これにより、zod との統合API フィルタリング を可能にし、より柔軟で効率的な OpenAPI ドキュメント生成が可能になりました。


前の記事:Nuxt3 ベストプラクティス02: Request のバリデーション

概要

技術的洞察、経験、思考を共有する個人ブログ

クイックリンク

お問い合わせ

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 CODE赤兎. 無断転載禁止