<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>API Development · 仁才徳</title>
    <link>https://jared.lynskey.co.nz/ja/tags/api-development/</link>
    <description>ソフトウェアエンジニアリングと開発組織のリーダーシップについて。評価、採用とチームの拡大、CI/CD、DevOps モニタリング、エージェンティック AI。ソウルより。</description>
    <generator>Hugo</generator>
    <language>ja</language>
    <copyright>© 2026 仁才徳</copyright>
    <lastBuildDate>Mon, 26 Oct 2026 00:00:00 +1200</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/ja/tags/api-development/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Orvalの使い方：OpenAPIスペックから型付きAPIクライアントとモックを自動生成する</title>
      <link>https://jared.lynskey.co.nz/ja/posts/2024/2024-04-10-openapitools/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ja/posts/2024/2024-04-10-openapitools/</guid>
      <pubDate>Wed, 10 Apr 2024 00:30:03 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>API Development</category>
      <category>Orval</category>
      <category>OpenAPI</category>
      <category>TypeScript</category>
      <category>React Query</category>
      <category>MSW</category>
      <category>Frontend</category>
      <category>Backend</category>
      <category>Teamwork</category>
      <category>Efficiency</category>
      <description>OrvalはOpenAPIスペックを読み込んで、型付きのTypeScriptクライアント、React Queryのフック、MSWのモックを生成してくれるので、APIレイヤーを手書きする人はいなくなります。セットアップの手順、モックの仕組み、openapi-generator・openapi-typescript・Hey APIとの比較をまとめました。</description>
      <content:encoded><![CDATA[<p>Orvalは、OpenAPI（Swagger）のスペックを読み込んで、フロントエンドのAPIレイヤーを代わりに書いてくれるコードジェネレーターです。エンドポイントごとの型付き関数かReact Queryのフック、コントラクトに正確に一致するTypeScriptのモデル、そして指定すれば、Fakerのデータが入ったMSWのモックハンドラーまで生成します。設定ファイルを1つコミットして<code>orval</code>を実行し、スペックが変わったらまた実行する。それだけです。</p>
<p>このシリーズの最初の記事で、「コードを書く前にOpenAPIのコントラクトに合意しよう」という話をしました。今回はそのコントラクトを実際に「元が取れるもの」にしてくれるツールの話です。</p>
<p>フロントエンドのAPIレイヤーを手書きする作業は、楽しいと公言する人がいない類の仕事です。バックエンドのドキュメントから型を書き写し、40回目の同じリクエストラッパーを書き、3週間後に誰かがフィールド名を変えて、呼び出しの半分が静かに壊れる。Orvalはこの仕事そのものを消してくれます。OpenAPIスペックを指定すれば、クライアントコードを生成してくれるのです。全エンドポイント分の型付き関数、コントラクトに正確に一致するモデル、そしてバックエンドが存在する前から開発に使えるモック。</p>

<h2 class="relative group">フロントエンドチームが気に入る理由
    <div id="フロントエンドチームが気に入る理由" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%95%e3%83%ad%e3%83%b3%e3%83%88%e3%82%a8%e3%83%b3%e3%83%89%e3%83%81%e3%83%bc%e3%83%a0%e3%81%8c%e6%b0%97%e3%81%ab%e5%85%a5%e3%82%8b%e7%90%86%e7%94%b1" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>過小評価されがちなのがモック生成です。スペックに合意した時点で、フロントエンドは偽のサーバーを立てて、UI全体をそれに向かって作り込めます。永遠に「もうすぐできます」のエンドポイントを待つ必要はありません。本物のバックエンドができたら、クライアントの向き先を変えるだけで、だいたいそのまま動きます。両側が同じドキュメントから作られているからです。</p>
<p>もうひとつの利点は、退屈なコードが「構造的に正しい」状態になることです。手書きのリクエストボディでフィールド名を打ち間違える人はいなくなります。そもそも手書きのリクエストボディを書く人がいなくなるので。</p>

<h2 class="relative group">バックエンドチームにも嬉しい理由
    <div id="バックエンドチームにも嬉しい理由" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%90%e3%83%83%e3%82%af%e3%82%a8%e3%83%b3%e3%83%89%e3%83%81%e3%83%bc%e3%83%a0%e3%81%ab%e3%82%82%e5%ac%89%e3%81%97%e3%81%84%e7%90%86%e7%94%b1" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>スペックが唯一の情報源になると、日々の会話が変わります。「このエンドポイントって何を返すんでしたっけ？」というチャットのスレッドの代わりに、フロントエンドは生成された型を読めばいい。バックエンド開発者は割り込まれる回数が減り、フィードバックは早く届くようになります。フロントエンドがモック相手に作ってみて「このAPI、使いにくいな」と感じたら、それが分かるのは本番投入後ではなく、まだ変更が安上がりなうちです。</p>

<h2 class="relative group">ワークフローへの組み込み方
    <div id="ワークフローへの組み込み方" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%af%e3%83%bc%e3%82%af%e3%83%95%e3%83%ad%e3%83%bc%e3%81%b8%e3%81%ae%e7%b5%84%e3%81%bf%e8%be%bc%e3%81%bf%e6%96%b9" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>けっして凝ったことはしていません。バックエンドがスペックを提案し、両チームでレビューし、フロントエンドはOrvalでクライアントとモックを生成して作り始め、バックエンドは同じコントラクトに沿って本物を実装する。スペックが変わったら両側で再生成すれば、何が壊れたかはコンパイラが正確に教えてくれます。この最後の部分こそが本質です。フロントエンドとバックエンドのズレが、本番のバグではなくコンパイルエラーになるのです。</p>

<h2 class="relative group">セットアップ
    <div id="セットアップ" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%bb%e3%83%83%e3%83%88%e3%82%a2%e3%83%83%e3%83%97" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>まずは開発用の依存関係としてインストールします。生成されたコードがimportするパッケージも一緒に入れておきましょう。React Queryのフックとモックを使うなら、TanStack Query、MSW、Fakerです。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install --save-dev orval msw @faker-js/faker
</span></span><span class="line"><span class="cl">npm install @tanstack/react-query</span></span></code></pre></div></div>
<p>次に、プロジェクトのルートに<code>orval.config.ts</code>を置きます。Orval公式ドキュメントの例とほぼ同じものです。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;orval&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">petstore</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">input</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">target</span><span class="o">:</span> <span class="s1">&#39;./openapi.yaml&#39;</span><span class="p">,</span> <span class="c1">// a path or a URL
</span></span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">output</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">mode</span><span class="o">:</span> <span class="s1">&#39;single&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">target</span><span class="o">:</span> <span class="s1">&#39;./src/api/petstore.ts&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">schemas</span><span class="o">:</span> <span class="s1">&#39;./src/api/model&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">client</span><span class="o">:</span> <span class="s1">&#39;react-query&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">httpClient</span><span class="o">:</span> <span class="s1">&#39;fetch&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">mock</span>: <span class="kt">true</span><span class="p">,</span> <span class="c1">// MSW handlers plus Faker data
</span></span></span><span class="line"><span class="cl">      <span class="nx">override</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">fetch</span><span class="o">:</span> <span class="p">{</span> <span class="nx">includeHttpResponseReturnType</span>: <span class="kt">false</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div>
<p>何が生成されるかは<code>client</code>で決まります。素の<code>fetch</code>や<code>axios</code>の関数、React Query・Vue Query・Svelte Query・SWR用のフック、Angularのサービス、Zodのスキーマなどです。<code>includeHttpResponseReturnType: false</code>の行は、fetchクライアントが<code>{ data, status }</code>ではなくレスポンスボディそのものを返すようにする設定で、コンポーネント側が欲しいのはたいていこちらです。</p>
<p>コマンドを誰も覚えなくて済むように、scriptsに登録しておきます。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;api&#34;</span><span class="p">:</span> <span class="s2">&#34;orval&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p><code>npm run api</code>でフックとモデルが書き出されます。あとはコンポーネントから普通のReact Queryのフックと同じように使うだけで、型は最初から埋まっています。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useListPets</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;./api/petstore&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">PetList() {</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">data</span>: <span class="kt">pets</span><span class="p">,</span> <span class="nx">isLoading</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useListPets</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">isLoading</span><span class="p">)</span> <span class="k">return</span> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="nx">Loading</span><span class="err">…</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">pets</span><span class="o">?</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">pet</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">&lt;</span><span class="nt">li</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">pet</span><span class="p">.</span><span class="nx">id</span><span class="p">}&gt;{</span><span class="nx">pet</span><span class="p">.</span><span class="nx">name</span><span class="p">}&lt;/</span><span class="nt">li</span><span class="p">&gt;)}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>スペックでフィールド名を変えて<code>npm run api</code>をもう一度実行すれば、<code>pet.name</code>を使っている箇所が全部赤くなります。上で書いたズレのチェックがこれで、実行コストはゼロです。</p>

<h2 class="relative group">モックを相手に開発する
    <div id="モックを相手に開発する" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%a2%e3%83%83%e3%82%af%e3%82%92%e7%9b%b8%e6%89%8b%e3%81%ab%e9%96%8b%e7%99%ba%e3%81%99%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p><code>mock: true</code>にすると、Orvalはエンドポイントごとに、レスポンスのスキーマどおりの形をしたFakerのデータを返すMSWのリクエストハンドラーと、それらをまとめて返す関数も書き出します。そのリストを、ブラウザならMSWの<code>setupWorker</code>に、テストなら<code>setupServer</code>に渡せば、アプリは最初からコントラクトに一致した偽のバックエンドを相手に動きます。各ハンドラーは上書き用の値も受け取れるので、テストで特定のペイロードや空のリスト、エラー状態を返したいときも、誰かがフィクスチャを手書きする必要はありません。</p>
<p>フロントエンドが初日から作業を始められるのは、この部分のおかげです。モックは誰かが別途メンテナンスしなければならない成果物ではありません。クライアントと同じスペックから毎回再生成されるので、スペックに置いていかれようがないのです。</p>

<h2 class="relative group">Orvalとopenapi-generator、openapi-typescript、Hey APIの比較
    <div id="orvalとopenapi-generatoropenapi-typescripthey-apiの比較" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#orval%e3%81%a8openapi-generatoropenapi-typescripthey-api%e3%81%ae%e6%af%94%e8%bc%83" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>どれもOpenAPIスペックから出発しますが、どこまで生成するかが違います。</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>生成するもの</th>
					<th>向いているケース</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>Orval</strong></td>
					<td>型付きのクライアント関数やフレームワーク用のフック（React Query、SWR、Vue、Svelte、Angular）、モデル、MSWのモック、Zodのスキーマ</td>
					<td>1つの設定ファイルからフックとモックをまとめて得たいTypeScriptのフロントエンド</td>
			</tr>
			<tr>
					<td><strong>openapi-generator</strong></td>
					<td>数十の言語のクライアントとサーバースタブ（TypeScriptも含む）</td>
					<td>同じスペックからJava、Kotlin、Swift、TypeScriptを生成しなければならないチーム</td>
			</tr>
			<tr>
					<td><strong>openapi-typescript</strong></td>
					<td>型のみ。小さな型付きfetchラッパーの<code>openapi-fetch</code>と組み合わせて使う</td>
					<td>型だけが欲しくて、ランタイムのコードはほとんど要らない場合</td>
			</tr>
			<tr>
					<td><strong>Hey API</strong>（<code>@hey-api/openapi-ts</code>）</td>
					<td>型付きのSDK。TanStack QueryやZodなどのプラグインあり</td>
					<td>Orvalと守備範囲が近く、プラグインの仕組みを中心に作られている</td>
			</tr>
	</tbody>
</table>
<p>型だけで足りるなら、openapi-typescriptが一番軽くて、文句のつけようがありません。openapi-generatorが真価を発揮するのは、1つのスペックから複数の言語向けにコードを生成しなければならないときです。TypeScriptの出力もちゃんと動きますが、読んでいると、まずJava向けに書かれたものだという感触があります。OrvalとHey APIのどちらにするかは、ほぼ好みと、どちらの出力コードを読みたいか次第です。私がOrvalを使い続けているのは、フックと同じ1回の実行でモックも出てくるからで、2つのチームの働き方を変えたのはまさにこの機能でした。</p>
<p>派手なツールではありません。ただ、チーム間の言い争いをひとつのカテゴリごと静かに消してくれる。それと引き換えなら、私はいつでも喜んで使います。</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2024/2024-04-10-openapitools/featured.jpg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
