Nuxt3 ベストプラクティス03: OpenAPI ドキュメント出力
公開日
2025年2月2日
更新日
2025年2月2日
14
分で読める
Vue
1. はじめに
Nuxt 3 では、Nitro サーバーの組み込み OpenAPI ドキュメント生成機能を活用し、Swagger UI や Scalar UI を用いて視覚的に表示できます。しかし、この機能には以下の問題があります。
- 実験的機能: Nitro の OpenAPI サポートはまだ実験段階であり、将来的に変更または削除される可能性があります。
- API フィルタリングの欠如: すべての Server API が公開され、特定の API を除外する設定がありません。
- 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 方法の概要
主な手順は以下のとおりです。
- OpenAPI レジストリを作成: API の定義を管理。
defineOpenAPIメソッドの提供: 各 API がこのメソッドを通じてレジストリに登録。- 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 ドキュメント生成が可能になりました。