OrvalでOpenAPI定義からコードを自動生成しスキーマ駆動でHonoアプリを開発する
OrvalはOpenAPI仕様をもとにTypeScriptのクライアントやサーバーコード、mswモック、zodスキーマを自動生成するツールです。
Hono周辺の機能追加および改善を進めておりv7.6.0では実用可能な段階に成長しました。
本記事ではOrvalを使ってHonoサーバーのソースコードを自動生成し、APIエンドポイントを実装する方法について解説します。
なお今回使用するサンプルアプリはリポジトリ内にサンプルアプリとしてコミットしています。
Orvalの設定ファイル
以下の様にorval.config.tsを定義します。
import { defineConfig } from 'orval';
export default defineConfig({
api: {
input: {
target: './petstore.yaml',
},
output: {
mode: 'tags-split',
client: 'hono',
target: 'src/endpoints',
schemas: 'src/schemas',
override: {
hono: {
compositeRoute: 'src/routes.ts',
validatorOutputPath: 'src/endpoints/validator.ts',
},
},
},
},
});
自動生成されるファイルの解説
orvalコマンドで自動生成されるディレクトリ構成は以下のようになります。
src/
├── endpoints
│ ├── pets
│ │ ├── pets.context.ts
│ │ ├── pets.handlers.ts
│ │ └── pets.zod.ts
│ └── validator.ts
├── routes.ts
└── schemas
├── pet.ts
└── pets.ts
schemas
OpenAPIに定義したリクエスト・レスポンスのスキーマ定義です。
endpoints/pets
設定ファイルでmode: tags-splitを指定しているためOpenAPIのtags定義毎にHonoのhandlerをディレクトリ分割して生成します。
handlerの実装として以下の3つのファイルを生成します。
pets.context.tspets.handlers.tspets.zod.ts
自動生成する度に全てのファイルを再生成してしまうと実装したコードが削除されてしまう為orvalではhandler.tsが既に存在する場合は上書きしません。
context.tsとzod.tsのみを最新のOpenAPIの定義に合わせて再生成することにより実装と分離しながらスキーマとの整合性を担保します。
pets.context.ts
リクエストのパラメータをhandlerで扱うためにHonoのContextをOpenAPIから自動生成しています。
pets.zod.ts
リクエストのパラメータ、レスポンスをバリデーションするためのzod schemaをOpenAPIから自動生成しています。
pets.handlers.ts
前述したContextとzod schemaを参照してHonoのhandlerを定義しています。
例えば、pets.handlers.ts は以下のようになっています。
import { createFactory } from 'hono/factory';
import { zValidator } from '../../validators/pets.validator';
import { ListPetsContext } from './pets.context';
import { listPetsQueryParams, listPetsResponse } from './pets.zod';
const factory = createFactory();
export const listPetsHandlers = factory.createHandlers(
zValidator('query', listPetsQueryParams),
zValidator('response', listPetsResponse),
async (c: ListPetsContext) => {}
);
handlerには、バリデーションやコンテキスト定義は含まれていますが実際の処理は未実装 です。
そのため、以下の様にレスポンスを返す処理を追加します。
import { createFactory } from 'hono/factory';
import { zValidator } from '../../validators/pets.validator';
import { ListPetsContext } from './pets.context';
import { listPetsQueryParams, listPetsResponse } from './pets.zod';
const factory = createFactory();
export const listPetsHandlers = factory.createHandlers(
zValidator('query', listPetsQueryParams),
zValidator('response', listPetsResponse),
- async (c: ListPetsContext) => {}
+ async (c: ListPetsContext) => {
+ return c.json([
+ {
+ id: 1,
+ name: 'doggie'
+ }
+ ]);
+ }
);
endpoints/validator.ts
スキーマと一致しないリクエスト、レスポンスの場合にエラーを返却する共通のバリデーションロジックです。
設定ファイルでoverride.hono.validatorOutputPathを指定する事でファイルの出力先を指定可能です。
全てのendpointsで参照する共通処理の為src/endpointsに出力する様に以下の様に定義しています。
override: {
hono: {
validatorOutputPath: 'src/endpoints/validator.ts'
}
}
doc: https://orval.dev/reference/configuration/output#validatoroutputpath
routes.ts
src/endpointsに出力されたHonoのhandlerとURLのパスを紐付けたHonoインスタンスです。
import { Hono } from 'hono';
import {
listPetsHandlers,
createPetsHandlers,
updatePetsHandlers,
showPetByIdHandlers
} from './endpoints/pets/pets.handlers';
const app = new Hono();
app.get('/pets', ...listPetsHandlers);
app.post('/pets', ...createPetsHandlers);
app.put('/pets', ...updatePetsHandlers);
app.get('/pets/:petId', ...showPetByIdHandlers);
export default app;
設定ファイルでoverride.hono.compositeRouteを指定する事でファイルの出力先を指定可能です。
override: {
hono: {
compositeRoute: 'src/routes.ts'
}
}
doc: https://orval.dev/reference/configuration/output#compositeroute
このファイルを出力することで常にOpenAPIに定義しているpathとHonoサーバーに定義しているルーティングを一致させることが可能です。
使い方
Honoサーバーapp.tsを用意します。
import { Hono } from 'hono';
import routes from './routes';
const app = new Hono();
export default app;
ここに自動生成されたroutes.tsを統合します。
import { Hono } from 'hono';
+ import routes from './routes';
const app = new Hono();
+ app.route('/', routes);
export default app;
app.route('/', routes);とする事でルーティングの定義を組み込むことができるので自動生成による影響を受けません。
devサーバーを起動して動作確認ができます。
yarn wrangler dev src/app.ts
curl http://localhost:8787/pets
#=> [{"id":1,"name":"doggie"}]
まとめ
mode: tags-splitの対応やcompositeRoute、validatePathオプションの追加により実用可能な段階に成長しました。
OpenAPI仕様をもとにHonoのAPIエンドポイントを自動生成し、型安全な実装を効率的に行うことで堅牢なスキーマ駆動が実現可能です。
Discussion