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.ts
pets.handlers.ts
pets.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