OrvalでOpenAPIからHonoとfetch APIクライアントを自動生成しREST APIでも堅牢なスキーマ駆動開発を実現する
はじめに
Orval
というOpenAPI
からREST API
のクライアントやzod
のバリデーション定義、msw
でのモック定義を自動生成するツールですがfetch API
とHono
もサポートされています。
それらの自動生成を用いる事でREST API
を用いてTypescript
でのバックエンド、フロントエンドを開発する際にも堅牢なスキーマ駆動開発を行う事ができると考えています。
この記事では以下のアプリケーションを構築しながらその方法について解説します。
-
Hono
でのAPIアプリケーション開発 -
fetch API
をNext.js
で使用したクライアントアプリケーション開発
解説
Orval
リポジトリにあるサンプルアプリケーションをベースに解説します。
アプリケーション構成
まずはアプリケーションの全体像を確認します。
アプリケーション全体は大きく以下の構成になっています。
- hono-app:
Hono
で作成したAPIアプリケーション - next-app:
Next.js
で作成したクライアントアプリケーション - orval.config.ts:
Orval
の設定ファイル - petstore.yaml:
OpenAPI
のスキーマファイル
Orval
によりpetstore.yaml
の定義に基づいたHono
の雛形をhono-app
に、fetch API
のクライアントをnext-app
にそれぞれ自動生成します。
$ pwd
orval/samples/hono/hono-with-fetch-client
$ tree -L 2
.
├── README.md
├── hono-app
│ ├── README.md
│ ├── node_modules
│ ├── package.json
│ ├── src
│ ├── tsconfig.json
│ └── wrangler.toml
├── next-app
│ ├── README.md
│ ├── app
│ ├── next-env.d.ts
│ ├── next.config.mjs
│ ├── node_modules
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── public
│ ├── tailwind.config.ts
│ └── tsconfig.json
├── node_modules
├── orval.config.ts
├── package.json
└── petstore.yaml
Orvalでの自動生成
サンプルアプリのルートディレクトリに存在するorval.config.ts
の設定は以下の通りで二つの自動生成の設定をしています。
- petstoreClient:
fetch API
を用いたAPI
クライアントの設定 - petstoreApi:
Hono
の雛形を生成するための設定
import { defineConfig } from 'orval';
export default defineConfig({
petstoreClient: {
input: {
target: './petstore.yaml',
},
output: {
mode: 'tags-split',
client: 'fetch',
target: 'next-app/app/gen/',
schemas: 'next-app/app/gen/models',
clean: true,
baseUrl: 'http://localhost:8787',
mock: true,
},
},
petstoreApi: {
input: {
target: './petstore.yaml',
},
output: {
mode: 'split',
client: 'hono',
target: 'hono-app/src/petstore.ts',
override: {
hono: {
handlers: 'hono-app/src/handlers',
},
},
},
},
});
各設定項目についての解説は割愛するので詳細はOrvalのドキュメントを参照してください。
orvalの実行
orval
を実行します。
yarn orval
🍻 Start orval v7.0.1 - A swagger client generator for typescript
🎉 petstoreClient - Your OpenAPI spec has been converted into ready to use orval!
🎉 petstoreApi - Your OpenAPI spec has been converted into ready to use orval!
petstore.yaml
の定義に基づきそれぞれのコードが自動生成されます。
-
Hono
の雛形がhono-app
に生成 -
fetch API
のクライアントがnext-app
に生成
Honoアプリケーション
自動生成されたHono
の雛形を確認します。
hono-app
はyarn create hono-app
で作成したプロジェクトをベースにしています。
src
配下に配置されているファイルがOrval
によって生成されたファイルです。
ファイル構成
cd hono-app
tree src/
src/
├── handlers
│ ├── createPets.ts
│ ├── listPets.ts
│ ├── showPetById.ts
│ └── updatePets.ts
├── index.ts
├── petstore.context.ts
├── petstore.schemas.ts
├── petstore.ts
├── petstore.validator.ts
└── petstore.zod.ts
- petstore.ts:
Hono
の初期化とエンドポイントの定義 - handlers: エンドポイントごとの処理を記述
- petstore.schemas.ts: リクエスト/レスポンスのスキーマ定義
- petstore.validator.ts: バリデーションの定義
- petstore.zod.ts: バリデーションのスキーマ定義
- petstore.context.ts: エンドポイントのコンテキスト定義
index.ts
はHono
プロジェクトを作成した際に作成されたファイルです。以後使用しないので削除しても問題ありません。
雛形の実装
handlers
に雛形が生成されているので各エンドポイントの処理を記述します。
ここではサンプルとしてhandlers/listPets.ts
にJSON
で固定値を返却するだけの実装を行います。
import { createFactory } from 'hono/factory';
import { zValidator } from '../petstore.validator';
import { ListPetsContext } from '../petstore.context';
import { listPetsQueryParams, listPetsResponse } from '../petstore.zod';
const factory = createFactory();
export const listPetsHandlers = factory.createHandlers(
zValidator('query', listPetsQueryParams),
zValidator('response', listPetsResponse),
async (c: ListPetsContext) => {
+ return c.json([
+ {
+ id: 1,
+ name: 'doggie',
+ tag: 'dog',
+ },
+ ]);
},
);
アプリケーションの起動
Honoアプリケーションを起動します。
yarn dev
http://localhost:8787
にアクセスする事でAPIのエンドポイントにアクセスできます。
curl http://localhost:8787/pets
[{"id":1,"name":"doggie","tag":"dog"}]
Next.jsアプリケーション
自動生成されたfetch API
のクライントを確認します。
next-app
はyarn create next-app
で作成したプロジェクトをベースにしています。
app/gen/
に配置されているファイルがOrval
によって生成されたファイルです。
tree app/gen/
app/gen/
├── models
│ ├── createPetsBodyItem.ts
│ ├── error.ts
│ ├── index.ts
│ ├── listPetsParams.ts
│ ├── pet.ts
│ └── pets.ts
└── pets
├── pets.msw.ts
└── pets.ts
- models: リクエスト/レスポンスのスキーマ定義
- pets/pets.ts: エンドポイントごとのクライアント処理を記述
- pets/pets.msw.ts: モックサーバーの設定
エンドポイントごとにディレクトリが作成され、クライアント処理とmsw
によるモック実装のファイルが作成されます。
zod
によるバリデーション定義も生成することも可能ですがサンプルでは割愛しているので興味があればOrvalのドキュメントを参照してください。
APIクライアントの使用
app/pets.tsx
を作成して以下の様にAPIクライアントを使用します。
import { listPets } from './gen/pets/pets';
export default async function Pets() {
const { data: pets, status } = await listPets();
return (
<div>
<h1 className="text-4xl">Pets by server actions</h1>
<ul>
{pets.map((pet) => (
<li key={pet.id}>tag: {pet.tag}</li>
))}
</ul>
<h2 className="text-xl">Status: {status}</h2>
</div>
);
}
listPets
関数はOrval
から自動生成された関数です。
export const listPets = async (
params?: ListPetsParams,
options?: RequestInit,
): Promise<listPetsResponse> => {
const res = await fetch(getListPetsUrl(params), {
...options,
method: 'GET',
});
const data = await res.json();
return { status: res.status, data };
};
Pet
コンポーネントはapp/page.tsx
で呼び出されています。
import Pets from './pets';
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<Pets />
</main>
);
}
アプリケーションの起動
Next.jsアプリケーションを起動します。
yarn dev
http://localhost:3000
にアクセスする事でクライアントアプリケーションにアクセスします。
Hono
で構築したAPI
にアクセスして取得したデータが表示されている事が確認できます。
Orvalのwatchオプション
最初に自動生成する際に実行したOrval
コマンドですがwatch
オプションがあります。
orval
コマンドに--watch
を付与して実行する事でwatchモードで起動し、OpenAPI
のスキーマファイルが変更された際に自動でクライアントコードを再生成することができます。
yarn orval --watch
🍻 Start orval v7.0.1 - A swagger client generator for typescript
Watching for changes in "/app/samples/hono/hono-with-fetch-client/petstore.yaml" | "/app/samples/hono/hono-with-fetch-client/petstore.yaml"
Change detected: add /app/samples/hono/hono-with-fetch-client/petstore.yaml
petstoreClient: Cleaning output folder
🎉 petstoreClient - Your OpenAPI spec has been converted into ready to use orval!
🎉 petstoreApi - Your OpenAPI spec has been converted into ready to use orval!
これにより以下の開発フローを実現することができREST API
でのシームレスなスキーマファースト開発を行うことができます。
-
OpenAPI
のスキーマファイルを変更 -
Hono
とfetch API
のクライアントコードが自動で再生成
実際に開発しているショートムービーをサンプルアプリのREADMEに添付しているのでご覧ください。
おわりに
fetch API
のクライアントコードをNext.js
で使い、Hono
で構築したREST API
を呼び出すアプリケーションを開発することができました。
これによりREST API
を用いてTypescript
でのバックエンド、フロントエンドを開発する際にも堅牢なスキーマ駆動開発をシームレスに行う事ができるのではないかと考えています。
Orval
は他にもOpenAPI
からクライアントコードを自動生成することができ様々なケースで活用できると思いますので是非試してみてください。
Discussion