Zod OpenAPI Hono と Hono OpenAPI を比較してみた
はじめに
Honoを使っている際に「Zod OpenAPI Hono」と「Hono OpenAPI」の区別がつかず混乱してしまったため、自分用に比較してみました!
概要や具体的なコードも載せていますので、読み飛ばしたい方は「まとめ 」を読んでいただければと思います!
間違い等ありましたらコメントいただけると幸いです。
Zod OpenAPI
ドキュメント:https://hono.dev/examples/zod-openapi
リポジトリ:https://github.com/honojs/middleware/tree/main/packages/zod-openapi
概要
Zod OpenAPI Honoは、HonoにZodを組み合わせて値や型のバリデーションとOpenAPIドキュメント生成を同時に行えるようにしたHonoの拡張クラスです。
APIのルートを定義すると、そのまま型安全なチェック Swagger仕様書の自動生成が可能になります。(READMEから翻訳・抜粋)
使い方
-
Zod でスキーマを定義
import { z } from '@hono/zod-openapi' const UserSchema = z.object({ id: z.string().openapi({ example: '123' }), name: z.string().openapi({ example: 'John Doe' }), age: z.number().openapi({ example: 42 }), }).openapi('User')-
.openapi()でメタ情報を付与しています
-
-
ルートを定義 (
createRoute)import { createRoute } from '@hono/zod-openapi' const route = createRoute({ method: 'get', path: '/users/{id}', responses: { 200: { content: { 'application/json': { schema: UserSchema } }, description: 'Retrieve the user', }, }, })-
content: { 'application/json': { schema: UserSchema } }
この一文がOpen API生成とバリデーションの両方を担っていますね! - Zodスキーマが唯一の情報源になっているようです
-
-
アプリに組み込み (
OpenAPIHono)import { OpenAPIHono } from '@hono/zod-openapi' const app = new OpenAPIHono() app.openapi(route, (c) => c.json({ id: '1', name: 'Ultra-man', age: 20 })) app.doc('/doc', { openapi: '3.0.0', info: { title: 'My API', version: '1.0.0' } })- 処理は
app.openapi(route, handler)に直接 handler を渡す形になります
- 処理は
Hono OpenAPI
ドキュメント:https://hono.dev/examples/hono-openapi
リポジトリ:https://github.com/rhinobase/hono-openapi
概要
hono-openapi は、Zod、Valibot、ArkType、TypeBoxといったバリデーションライブラリと統合することで、Hono APIのOpenAPIドキュメントを自動生成できるミドルウェアです。(ドキュメントから翻訳・抜粋)
使い方
-
スキーマを定義(例: Valibot)
import * as v from 'valibot' const querySchema = v.object({ name: v.optional(v.string()) }) const responseSchema = v.string()
-
ルートを定義 (
describeRoute)import { Hono } from 'hono' import { describeRoute, resolver, validator } from 'hono-openapi' const app = new Hono() app.get( '/', describeRoute({ description: 'Say hello to the user', responses: { 200: { content: { 'text/plain': { schema: resolver(responseSchema) } } }, }, }), validator('query', querySchema), (c) => c.text(`Hello ${c.req.valid('query')?.name ?? 'Hono'}!`) )-
resolver(responseSchema)でOpen APIに変換しています -
validator('query', querySchema)でバリデーションを行っています
→ ドキュメント作成とバリデーションを別々に書くので、吐き出されたOpen APIと実装が食い違う余地がありそうです - 処理は
describeRoute({ ... })でOpenAPI ドキュメントの仕様を定義する形になります
-
-
OpenAPI ドキュメントを公開
import { openAPIRouteHandler } from 'hono-openapi' app.get('/openapi', openAPIRouteHandler(app, { documentation: { info: { title: 'Hono API', version: '1.0.0' }, servers: [{ url: 'http://localhost:3000', description: 'Local Server' }], }, }))-
app.get(..., handler)というように通常のHonoと同じ形式で処理を書きます
-
まとめ
| 項目 | Zod OpenAPI Hono | Hono OpenAPI |
|---|---|---|
| サポートライブラリ | Zod専用 | Zod / Valibot / ArkType / TypeBox など複数対応 |
| スキーマの役割 | 1つのスキーマで バリデーション+ドキュメント両方 を担う | 実装用とドキュメント用に 同じスキーマを両方渡す必要あり(共通化すればズレない) |
| ルート定義 |
createRoute で定義 |
app.get(path, describeRoute(...), validator(...), handler) で定義 |
| 処理の書き方 |
app.openapi(route, handler) に handler を直接渡す
|
app.get(..., handler) という 通常の Hono と同じ形式
|
「Zod OpenAPI Hono」はZod専用でシンプルに完結するのが特徴で、型定義からバリデーション・ドキュメント生成までを一元管理でき、よりスキーマ駆動な開発体験ができるのかなと感じています。
対して「Hono OpenAPI」は複数のスキーマライブラリに対応している反面、resolverとvalidatorの両方にスキーマを渡す必要があり、ほんの少し冗長に感じられるかもしれません。(逆に何かしらの目的で異なるスキーマを渡せるという意味でもある)
棲み分けとしては、
- Zodを前提にスキーマを単一ソースとして扱いたい場合には「Zod OpenAPI」
- Zod含め、それ以外のバリデーションライブラリを使用したい場合や、通常のHonoの書き心地で実装をしたい場合には「Hono OpenAPI」
というようになるのかなと思います!
Discussion