OpenAPI ドキュメントの追加

Hono REST テンプレートには /docsScalar UI/openapi.json の OpenAPI 3.1 仕様が同梱されており、@hono/zod-openapi によって動作しています。このレシピでは、各構成要素がどう連携しているか、そして生成される仕様をどう拡張するかを説明します。

提供されるもの

  • src/ZodOpenapi.res@hono/zod-openapi の ReScript バインディング

  • src/Scalar.res@scalar/hono-api-reference のバインディング

  • src/Server.res/openapi.json/docs の配線

  • src/Routes/Users.res — 仕様に公開された users CRUD

pnpm dev の後に http://localhost:3000/docs を開くと、インタラクティブな UI を確認できます。

全体の配線

Zod schema ──► @hono/zod-openapi createRoute ──► app.openapi(route, handler)
                                                      │
                                                      ├─► /openapi.json (spec)
                                                      └─► Route handler (validated input/output)

/openapi.json ──► Scalar.apiReference ──► /docs (interactive UI)

Zod スキーマの定義

// src/Schemas.res
let createUser = ZodOpenapi.object({
  "name": ZodOpenapi.string(~minLength=1, ()),
  "email": ZodOpenapi.string(~format="email", ()),
})

let user = ZodOpenapi.object({
  "id": ZodOpenapi.number(),
  "name": ZodOpenapi.string(),
  "email": ZodOpenapi.string(~format="email", ()),
})

ルートにアタッチする

let route = ZodOpenapi.createRoute({
  "method": "post",
  "path": "/users",
  "request": {
    "body": {
      "content": {
        "application/json": {"schema": Schemas.createUser},
      },
    },
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {"schema": Schemas.user},
      },
    },
    "400": {"description": "Validation error"},
  },
})

app->ZodOpenapi.openapi(route, async ctx => {
  let body = ctx->ZodOpenapi.validBody
  /* body is typed from Schemas.createUser */
})

API を記述する

Scalar UI にタイトル、説明、サーバー一覧を表示させるためのメタデータを追加します:

// src/Server.res
app->ZodOpenapi.doc("/openapi.json", {
  "openapi": "3.1.0",
  "info": {
    "title": "My API",
    "version": "1.0.0",
    "description": "User management service.",
  },
  "servers": [
    {"url": "http://localhost:3000", "description": "Local"},
    {"url": "https://api.example.com", "description": "Production"},
  ],
})

タグでルートをグループ化する

タグは Scalar UI がサイドバーでエンドポイントをどうグループ化するかを制御します:

let route = ZodOpenapi.createRoute({
  "method": "get",
  "path": "/users",
  "tags": ["Users"],
  /* ... */
})

セキュリティ

Bearer トークン認証を仕様に組み込みます:

app->ZodOpenapi.registerComponent("securitySchemes", "BearerAuth", {
  "type": "http",
  "scheme": "bearer",
  "bearerFormat": "JWT",
})

let protectedRoute = ZodOpenapi.createRoute({
  /* ... */
  "security": [{"BearerAuth": []}],
})

Scalar UI は "Authorize" ボタンを表示し、訪問者がトークンを貼り付けられるようにします。

仕様のエクスポート

curl http://localhost:3000/openapi.json > openapi.json で仕様をディスクに書き出せます。これを CI に組み込んで仕様がコミット間で安定していることを検証したり、クライアントジェネレータ (openapi-generatororval@hey-api/openapi-ts) に渡したりできます。

UI の切り替え

Scalar がデフォルトですが、/openapi.json の仕様は任意の OpenAPI レンダラーで使用できます:

src/Server.resScalar.apiReference を同等のハンドラに置き換えてください。

よくある落とし穴

  • ルートが /docs に表示されないapp.get(...) ではなく app.openapi(route, handler) を使用してください。通常の Hono ルートは仕様に現れません。

  • Scalar UI が空白 — ブラウザコンソールを確認してください。多くの場合、/openapi.json が JSON ではなく HTML (エラーページ) を返しています。直接アクセスして実際のエラーを確認してください。

  • Zod スキーマの不一致 — レスポンスハンドラが Zod で変換できない形状を返すと、バリデーションエラーで 500 になります。実行時の形状に一致させるには ZodOpenapi.string(~format="email", ()) のようなヘルパーを使ってください。