🗿

【Orval】APIスキーマからほぼノーコードでAPI/クライアント/MCPを生成する

に公開

はじめに

こんにちは。とあるモアイ好きの FE エンジニアです🗿

今回は Orval というライブラリを使い、OpenAPI スキーマから

  • API
  • API クライアントコード
  • MCP サーバー

の TypeScript コード を自動生成し、ほぼ追加コード記述なしで、「チャット入力→ API 呼び出し」「画面描画→ API 呼び出し」を実現します。

また本記事は1から真似して実行できるようになっているので、ぜひ記事を見ながら手を動かして Orval を使ったスキーマ駆動開発を体感してみてください。


対象読者

  • TypeScript で開発を行なっている方
  • Orval を用いたコード生成について知りたい方
  • RESTful API を呼び出す MCP サーバー について興味がある方

本記事で使用するもの

  • Cursor (エディタ, MCP クライアントとして使用)
  • npm (パッケージマネージャ。npx コマンドを MCP サーバーの起動等に使用)
  • Next.js (フロント画面および Route Handlers を API サーバーとして使用)
  • 各種ライブラリ
    • orval, hono, zod, modelcontextprotocol(mcp), ...

Orval で何ができる?

https://orval.dev/

Orval とは、OpenAPI スキーマ (.yaml.json) から API クライアントコードを自動生成するライブラリです。

OpenAPI スキーマから API クライアントコードを自動生成するツールは他にもたくさんあるのですが、それらに比べて Orval はよりフロントエンドで扱いやすいものに特化しています。

生成できるのは API クライアントコード"だけ"ではない

API クライアントコードを自動生成するライブラリです。

と前述しましたが、早速訂正します。

最近の Orval は OpenAPI スキーマから 「API (正確には雛形) 」「MCP Server」 のコード生成も可能です。

本記事ではこちらも活用しフロントもバックエンドも Orval の生成コードを元に作ります。

詳細は公式ドキュメントや、Orval の Maintainer の方の記事もあるのでぜひご覧ください

https://zenn.dev/soartec_lab/articles/43d318b0cfd1c5

https://zenn.dev/soartec_lab/articles/e88c0af7e51522

本記事で実践する構成

本記事で最終的に以下の構成図のものが出来上がります。

紫色 の部分が Orval で生成できる部分になります。(API サーバーは一部なので点線)
こう見ると、ほとんどが自動生成でカバーできてしまいますね。

  • API サーバー
    • Next.js (App Router) の Route Handlers と、Orval で自動生成される Orval で全て自動生成します。 のコードを使います。
    • ハンドラー部分は自動生成させ、実際のロジックは手動で書きます。
  • MCP サーバー
    • Orval で全て自動生成します。
  • MCP クライアント
    • 今回は Cursor を使います。Cursor では設定ファイルを一つ作るだけで簡単に MCP サーバーとの接続が可能です。
  • リクエスト/レスポンス処理 (図の矢印部分)
    • Orval で全て自動生成します。fetch で HTTP リクエストを送受信し、zod でバリデーションされます。
  • ユーザ画面
    • React で書きますが、Orval により生成された関数を呼ぶだけなので、本記事ではほぼコードを書きません。
  • データベース
    • 本題では無いので今回はただの変数で代用します。

実践

1. Next.js と Orval のインストール

まず 公式の手順で Next.js をインストールします。

npx create-next-app@latestでも良いですが、色々選択肢が出てきて面倒なので--yesオプションつけてスキップしちゃいます。

npx create-next-app@latest --yes --empty 好きなプロジェクト名

実行が完了したらnpm run devで 起動するか確認しましょう。

cd 作成したプロジェクト名
npm run dev

また本日の主役の Orval もインストールしておきます。

npm i -D orval

2. OpenAPI スキーマファイルの用意

  /
  ├─ app/
  ├─ node_modules
  ├─ package-lock.json
  ├─ package.json
+ ├─ schemas.yaml
  └─ tsconfig.json

まず Orval の生成元となる OpenAPI スキーマファイル schemas.yaml (.json も可) を用意します。

本記事では以下の「カラフルモアイ API」スキーマを使用します。


※ちょっと長いので折りたたんでます

schemas.yaml
schemas.yaml
openapi: 3.0.0
info:
  title: カラフルモアイAPI
  description: カラフルなモアイのリストを管理するAPI。
  version: 1.0.0
paths:
  /colorful-moai:
    get:
      summary: カラフルモアイ一覧を取得
      operationId: getColorfulMoai
      tags:
        - colorful-moai
      responses:
        "200":
          description: カラフルモアイの一覧
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetColorfulMoaiResponse"
    post:
      summary: 新しいカラフルモアイを追加
      operationId: addColorfulMoai
      tags:
        - colorful-moai
      requestBody:
        description: 追加するカラフルモアイ。
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AddColorfulMoaiBody"
      responses:
        "200":
          description: 更新されたカラフルモアイの一覧
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AddColorfulMoaiResponse"
components:
  schemas:
    AddColorfulMoaiBody:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          example: "黄モアイ"
    AddColorfulMoaiResponse:
      type: array
      items:
        type: string
      example: ["赤モアイ", "緑モアイ", "青モアイ", "黄モアイ"]
    GetColorfulMoaiResponse:
      type: array
      items:
        type: string
      example: ["赤モアイ", "緑モアイ", "青モアイ"]

ざっくり書いてあることとしては

  • GET /colorful-moai:カラフルモアイのリストを返す
    • RequestBody: なし
    • Response:["赤モアイ", "緑モアイ", "青モアイ"]
  • POST /colorful-moai:カラフルモアイをリストに追加し、その後のリストを返す
    • RequestBody: "黄モアイ"
    • Response:["赤モアイ", "緑モアイ", "青モアイ", "黄モアイ"]

というシンプルなスキーマとなっています。

3. Orval 設定ファイル orval.config.ts の作成

  /
  ├─ app/
  ├─ node_modules
+ ├─ orval.config.ts
  ├─ package-lock.json
  ├─ package.json
  ├─ schemas.yaml
  └─ tsconfig.json

続いて Orval の設定ファイル orval.config.ts を用意します。
こちらが自動生成の仕方の全てを決める本記事の最重要ファイルです。


※ちょっと長いので折りたたんでます

orval.config.ts
orval.config.ts
import { defineConfig } from "orval";

export default defineConfig({
  api: {
    input: "schemas.yaml", // 使用するOpenAPIスキーマ
    output: {
      target: "endpoints/",
      schemas: "schemas/",
      client: "hono", // 生成するコードの種類
      mode: "tags-split",
      override: {
        hono: {
          compositeRoute: "app/api/[[...route]]/route.ts", // Next.jsのRoute Handlersで使うためのPATH名
          validatorOutputPath: "endpoints/validator.ts",
        },
      },
    },
    hooks: {
      afterAllFilesWrite: "npx prettier . --write",
    },
  },
  mcp: {
    input: "schemas.yaml",
    output: {
      target: "mcp/handlers.ts",
      schemas: "schemas/",
      client: "mcp", // 生成するコードの種類
      mode: "single",
      baseUrl: "http://localhost:3000/api", // APIサーバーのbaseUrl
      clean: true,
    },
    hooks: {
      afterAllFilesWrite: "npx prettier . --write",
    },
  },
  client: {
    input: "schemas.yaml",
    output: {
      target: "client/",
      schemas: "schemas/",
      client: "fetch", // 生成するコードの種類
      mode: "split",
      baseUrl: "http://localhost:3000/api", // APIサーバーのbaseUrl
      clean: true,
    },
    hooks: {
      afterAllFilesWrite: "npx prettier . --write",
    },
  },
});

すこし解説すると orval.config.ts は「api」「mcp」「client」の3つで構成されています。

orval.config.ts (一部の行を省略済み)
/**
 * 説明のため一部の行を省略しています。こちらの記述では正しく動作しません。
 */
export default defineConfig({
  api: {
    output: {
      target: "endpoints/",
      client: "hono", // 生成するコードの種類
    },
  },
  mcp: {
    output: {
      target: "mcp/handlers.ts",
      client: "mcp", // 生成するコードの種類
      baseUrl: "http://localhost:3000/api", // APIサーバーのbaseUrl
    },
  },
  client: {
    output: {
      target: "client/",
      client: "fetch", // 生成するコードの種類
      baseUrl: "http://localhost:3000/api", // APIサーバーのbaseUrl
    },
  },
});

defineConfig の中の xxx.output にそれぞれの生成オプションを記述し、client: xxx にどの種類のコードを生成するかを指定します。今回の場合は以下です。

  • 「api」: Hono を使った API コードを生成
  • 「mcp」: MCP を使った MCP サーバーコードを生成
  • 「client」: fetch を使った API クライアントコードを生成

4. Orval のコード生成を実行

では下準備もほぼできたので Orval の生成コマンドを実行します。

package.jsonscripts にコマンドを追加し、実行します。

package.json
{
  "scripts": {
+    "generate": "orval --config ./orval.config.ts"
  },
npm run generate

実行すると以下のようにたくさんのファイルが生成されます。

生成されたファイル群 (一部省略)
  /
  ├─ app/
+ │  ├─ api/
+ │  │  └─ [[...route]]/
+ │  │     └─ route.ts
  │  └─ page.tsx
+ ├─ client/
+ │  └─ api.ts
+ ├─ endpoints/
+ │  ├─ colorful-moai/
+ │  │  └─ ...
+ │  └─ validator.ts
+ ├─ mcp
+  │  ├─ handlers.ts
+  │  ├─ http-client.ts
+  │  ├─ server.ts
+  │  └─ tool-schemas.zod.ts
  ├─ node_modules
+ ├─ schemas/
+ │  ├─ index.ts
+ │  └─ ...
  ├─ orval.config.ts
  ├─ package-lock.json
  ├─ package.json
  ├─ schemas.yaml
  └─ tsconfig.json

5. 生成されたファイルにコード追加

このままでは動作させるのに完全では無いので、すこし手を加えます。

まず生成された各種ファイルで使用されているライブラリをインストールします。

npm i hono zod @modelcontextprotocol/sdk @hono/zod-validator

5.1 ハンドリング部分のコード追加

つづいて既に Orval で自動生成されたファイル app/api/[[...route]]/route.ts をすこし修正します。

app/api/[[...route]]/route.ts
/**
 * Generated by orval v7.9.0 🍺
 * Do not edit manually.
 * カラフルモアイAPI
 * カラフルなモアイのリストを管理するAPI * OpenAPI spec version: 1.0.0
 */
  import { Hono } from "hono";
  import {
    getColorfulMoaiHandlers,
    addColorfulMoaiHandlers,
  } from "../../../endpoints/colorful-moai/colorful-moai.handlers";
+ import { handle } from "hono/vercel";

- const app = new Hono();
+ const app = new Hono().basePath("/api");

  app.get("/colorful-moai", ...getColorfulMoaiHandlers);
  app.post("/colorful-moai", ...addColorfulMoaiHandlers);

- export default app;

+ export const GET = handle(app);
+ export const POST = handle(app);

Next.js App Router では app 下に route.ts を作ることで 簡単に API エンドポイントを実装可能です。
どのエンドポイントでどんな関数を呼び出すかは すでに Hono のコードが生成されていましたので、残りのハンドリングする機構を Hono のミドルウェア (hono/vercel) を使うことで実現します。

Next.js の Route Handlers で Hono を使う方法について詳しく知りたい方は下記記事が参考になります。

https://zenn.dev/chot/articles/e109287414eb8c

5.2 API 本処理実装

つづいて API の処理を追加します。
上記の app/api/[[...route]]/route.ts で import されている endpoints/colorful-moai/colorful-moai.handlers.ts に処理を追記します。

endpoints/colorful-moai/colorful-moai.handlers.ts
...
  import {...

// 超簡易データベース
+ const COLORFUL_MOAI_LIST = ["赤モアイ", "緑モアイ", "青モアイ"];

  const factory = createFactory();
  export const getColorfulMoaiHandlers = factory.createHandlers(
    zValidator("response", getColorfulMoaiResponse),
    async (c: GetColorfulMoaiContext) => {
+     return c.json(COLORFUL_MOAI_LIST);
    }
  );
  export const addColorfulMoaiHandlers = factory.createHandlers(
    zValidator("json", addColorfulMoaiBody),
    zValidator("response", addColorfulMoaiResponse),
    async (c: AddColorfulMoaiContext) => {
+     const body = await c.req.json();

+     COLORFUL_MOAI_LIST.push(body["name"]);

+     return c.json(COLORFUL_MOAI_LIST);
    }
  );

今回は説明のためすごく簡易的に書いています (DB なんてただの変数です...) が、
本来であれば、ここに API の本処理 (DB とのやりとりや、その他ロジック処理) をしっかり書くことになります。

ですが OpenAPI スキーマに沿って Orval がほとんど自動生成されているので、比較的楽に API 実装を行うことができます。

5.3 クライアントコード実装

初期 Next.js プロジェクトは「Hello world!」と表示されるだけなので、API を叩いて結果を表示するようにしましょう。
API を叩く関数は Orval が生成してくれているので、ここではそれを呼び出して表示するだけです。

app/page.tsx
+ import { getColorfulMoai } from "@/client/api";

- export default function Home() {
+ export default async function Home() {

+   const res = await getColorfulMoai({
+     cache: "no-cache",
+   });

    return (
      <main>
-       <div>Hello world!</div>
+       <div>{JSON.stringify(res.data)}</div>
      </main>
    );
  }
  • 今回の構造の場合、fetch に cache: "no-cache" を指定しないと Next.js のビルド時にエラーが発生するので注意です (参考1, 参考2)

5.4 Cursor と MCP サーバーの接続設定

Cursor と Orval で生成された MCP サーバーを接続します。
Cursor では .cursor/mcp.json を作成することで簡単に MCP サーバーとの接続が可能です。

  /
+ ├─ .cursor/
+ │  └─ mcp.json
  ├─ app/
  │  ├─ api/
...
.cursor/mcp.json
{
  "mcpServers": {
    "colorsAPIServer": {
      "command": "npx",
      "args": [
        "tsx",
        "~~~~~~~~/mcp/server.ts" // 絶対パスにする
      ]
    }
  }
}

mcp.json の作成後、Cursor Settings > MCP > MCP Servers に表示されるカードを、enable にして緑に点灯されれば接続完了です。

動作確認

実際に Next.js プロジェクトを起動させ、API および 画面表示 (http://localhost:3000) を確認しましょう。

npm run dev

app/api/[[...route]]/route.ts に実装した API が app/page.tsx にて叩かれ、画面に表示できていますね。

Cursor のチャット (MCP クライアント) からも挙動を確認してみましょう。

試しに「カラフルモアイをとってきて」とチャットに入力してみます。

getColorfulMoai を呼ぶべきと判断され、実際に呼び出すことで GET リクエストをし値をとってくることができました。

つづいて POST リクエストもしてみます。
試しに「紫モアイをついかして」とチャットに入力してみます。

こちらも addColorfulMoai という POST リクエストを送る関数を実行すべきと判断され、値を追加することができました。
また画面側 (http://localhost:3000) でも変更が反映されていることが確認できます。

まとめ

今回は Orval を使い、OpenAPI スキーマから「API」「API クライアントコード」「MCP サーバー」を自動生成する手法を紹介しました。

Orval を用いることで、OpenAPI スキーマと config ファイル (orval.config.ts) 一つから、さまざまなコード生成が可能になり、爆速で API 周りの処理を構築することができます。

本記事で完成したコードは以下 CodesandBox にて公開しています。

https://codesandbox.io/p/devbox/5j2zy6

クローンしていただき、.cursor/mcp.json のパスだけ書き換えていただければ、ローカルの Cursor で MCP サーバー の動作も可能です。

Discussion