【Next.js + Orval + MSW】型安全 API クライアント & モック環境構築
こんにちは。チームラボのフロントエンド班の志田です。好きなものはモアイです🗿
今回初めて技術記事を書いてみました。
Next.js、Orval、MSW に関して気になっている方、ちょっと長いですが、ぜひ最後までお付き合いください!
はじめに
フロントエンド開発において、RESTful API を呼び出す処理(API クライアントコード)を作るのは結構手間がかかります。GET なのか POST なのか、リクエストやレスポンスの型は何なのかなど、考慮することが多いです。
といったことから近年では OpenAPI スキーマから、型定義や API クライアントコードを自動で生成するツールがよく使われています。
Orval は、OpenAPI のスキーマから、型安全な API クライアントコードを自動で生成してくれるツールです。
さらには、MSW で利用可能な API モック関数をスキーマから同時に生成することも可能です。
この記事では、Next.js(App Router)プロジェクトに Orval と MSW を導入し、効率的で型安全な API 連携とモック環境を構築する具体的な手順を説明していきます。
対象読者
この記事は、以下のような方を主な対象としています。
- Next.js(特に App Router)と TypeScript を用いたフロントエンド開発を行なっている方
- API モックの導入・運用方法を探している方
- Orval や MSW の導入を検討していて、基本的な使い方を知りたい方
本記事の主な環境
本記事で紹介する手順で使用する主要なものは、以下の環境で動作確認しています。
- node: 23.9.0
- npm: 11.1.0
- next: 15.3.1,
- react: 19.0.0,
- @tanstack/react-query: 5.74.4,
- msw: 2.7.5,
- orval: 7.8.0,
なぜ Orval なのか
Orval については、公式ドキュメントや以下の記事で詳しく解説されています。
その上で自分が Orval を使用する理由としては以下です。
- JavaScript 環境で HTTP リクエストを送るための fetch、Axios に両方対応している
- MSW 向けのモックハンドラーおよびモックデータも自動生成可能
- TanStack Query を使ったカスタムフックが自動生成可能
- 生成されるコードも比較的シンプルで無駄が少なく、カスタマイズもしやすい
また Orval 以外の API クライアントコード生成ツールとしては以下のようなものがあります。
-
OpenAPI-Generator(openapi-generator-cli)
- 多種多様な言語に対応。TypeScript(JavaScript)以外が必要な場合はこっちが良いかも
-
openapi-typescript, openapi-fetch
- Orval よりもさらにシンプル。API クライアントとしては fetch のみ対応
ご自身の用途に合わせてツールを選ぶのが良いですが、Orval は設定ファイル(後述)によるカスタマイズが豊富なため、近年のフロントエンド技術ではほとんどのケースで問題なく使えるのかなと思います。
Orval の導入
この章から実際に Next.js(App Router)プロジェクトに Orval を導入し、設定していく手順を説明していきます。
1. OpenAPI スキーマファイルの用意
まず最初に、API の設計図となる OpenAPI スキーマファイルが必要です。
実際の開発で使用するものでも良いですし、無い場合は Swagger 公式のサンプルや、AI に生成させるのも良いでしょう。
ファイル形式は yaml または json のどちらでも構いません。
ここでは例として、プロジェクトルートに schemas フォルダを作成し、その中に openapi.yaml という名前で配置するものとします。
my-next-app/
├─ app/
├─ schemas/
+ │ └─ openapi.yaml
├─ next.config.js
├─ package.json
└─ tsconfig.json
今回使用する openapi.yaml サンプル(色を返すGETメソッド一つのみの、シンプルなAPI)
openapi: 3.0.0
info:
title: Colors API
description: 色API
version: 1.0.0
servers:
- url: http://localhost:7777
# components セクションを追加し、スキーマを定義
components:
schemas:
# 成功レスポンス(200)のスキーマ
ColorListResponse:
description: 色の文字列の配列
type: array
items:
type: string
example: ["red", "green", "blue"] # スキーマレベルで Example を定義
# エラーレスポンス(500など)の共通スキーマ
ErrorResponse:
description: 標準的なエラーレスポンス
type: object
properties:
message:
type: string
description: エラーの詳細メッセージ
required: # message は必須とする場合
- message
example: # スキーマレベルで Example を定義
message: Internal Server Error
paths:
/api/colors:
get:
summary: 色の一覧を返す
description: 色を含む配列を取得します
operationId: getApiColors # Orval で生成される関数名を明示的に指定(推奨)
tags: # APIのグルーピング用(任意)
- Colors
responses:
# 200 レスポンス: components/schemas/ColorListResponse を参照
"200":
description: 色の配列(成功)
content:
application/json:
schema:
$ref: "#/components/schemas/ColorListResponse" # $ref で参照
# 500 レスポンス: components/schemas/ErrorResponse を参照
"500":
description: サーバーエラー
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse" # $ref で参照
またスキーマファイルの中身は OpenAPI v3 の仕様に沿って記述されている必要があります。
2. Orval のインストール
次に、Orval をインストールします。(本記事では以降 npm を使います)
$ npm install orval --save-dev
3. 設定ファイル(orval.config.ts)の作成と基本設定
Orval の動作は設定ファイルによって制御します。プロジェクトルートに orval.config.ts を作成します。
my-next-app/
├─ app/
├─ schemas/
│ └─ openapi.yaml
+ ├─ orval.config.ts
├─ next.config.js
├─ package.json
└─ tsconfig.json
orval.config.ts はかなりカスタマイズ可能です。(より詳細は公式 Doc )
基本的な設定として、入力となるスキーマファイルの場所と、生成されるコードの出力先を指定することで、とりあえず生成するだけなら以下のような記述だけでも利用可能です。
import { defineConfig } from "orval";
export default defineConfig({
// 任意の名前
color_api: {
input: "./schemas/openapi.yaml", // OpenAPI のスキーマファイルの PATH
output: "./apiClient/colors", // 生成するファイル群の PATH
},
});
ですが実際これで使うのは Orval を使いこなせてなくもったいないので、もうすこし肉付けしましょう。以下今回使用するサンプルです。
import { defineConfig } from "orval";
export default defineConfig({
// 任意の名前
color_api: {
input: {
target: "./schemas/openapi.yaml", // OpenAPI のスキーマファイルのパス
},
output: {
target: "./apiClient/colors", // 生成するファイル群のパス
schemas: "./apiClient/schemas", // スキーマファイルのパス
client: "react-query", // 生成するクライアントの種類
httpClient: "fetch", // 生成する HTTP クライアントの種類
mode: "split", // 生成するファイルを分割するか
clean: true, // 生成するファイルをクリーンアップするか,
// orval で生成する mock の設定
mock: {
type: "msw",
useExamples: true, // openapi.yaml の example を mock データとして使用するか(無い場合は faker.js で mock データが生成される)
},
override: {
mutator: {
path: "./apiClient/customFetch.ts", // カスタムfetchのパス
name: "customFetch",
},
fetch: {
includeHttpResponseReturnType: false, // false: fetch の返却値をResponseのデータの型にする
},
mock: {
required: true, // 自動生成で返却される mock データを必須にする
},
},
},
hooks: {
// ここは、プロジェクトで使用しているフォーマッターに合わせて設定してください
// afterAllFilesWrite: 'prettier --write', // 例: prettier 生成したファイルを整形
afterAllFilesWrite: "npx @biomejs/biome format --write", // 例: biome 生成したファイルを整形
},
},
});
-
output.client: クライアントコンポーネントでは TanStack Query を活用したいためreact-queryを指定しています。 -
output.httpClient: 使用環境が Next.js App Router なので、Data Cache を最大限扱うためにもfetchを使用します。 -
output.mode: yaml ファイルのサイズが大きい場合tags-splitをお勧めしますが、今回はサンプルのopenapi.yaml自体が小さいためsplitにしています。 -
output.override.mutator: 設定しないとただの fetch で処理をすることになるので基本的には custom の fetch 関数をつくり、ここで設定することをお勧めします(後述) -
hooks.afterAllFilesWrite: 今回コピペで動かすのを想定して npx コマンドのbiomeにしてますが、prettierなどローカルで使用しているものでフォーマットすることをお勧めします。
custom instance(customFetch.ts)の作成
API リクエストに共通のヘッダー(認証トークンなど)を付与したり、ベース URL を一元管理したりするために、先ほどの orval.config.ts の output.override.mutator に指定した customFetch.ts ファイルを作成します。
my-next-app/
+ ├─ apiClient/
+ │ └─ customFetch.ts
├─ app/
├─ schemas/
│ └─ openapi.yaml
├─ orval.config.ts
├─ next.config.js
├─ package.json
└─ tsconfig.json
export const customFetch = async <TData>(
url: string,
options: RequestInit = {}
): Promise<TData> => {
const baseUrl = "http://localhost:7777"; // 環境変数などから取得するのが望ましい
const requestUrl = new URL(url, baseUrl);
const headers = {
"Content-Type": "application/json", // デフォルトのヘッダー
...options.headers,
// 必要に応じて認証トークンなどを追加
// Authorization: `Bearer YOUR_TOKEN`,
};
try {
const response = await fetch(requestUrl, {
...options,
headers,
});
// エラー時: 今回はシンプルにステータスのみを含むエラーを throw
if (!response.ok) {
throw new Error(
`API request failed with status ${response.status}: ${response.statusText}`
);
}
// 成功時: レスポンスを JSON としてパース
const data: TData = await response.json();
return data;
} catch (error) {
console.error("customFetch Error:", error);
throw error;
}
};
今回はできるだけシンプルにしましたが、各自必要に応じて baseUrl 、共通処理やエラーハンドリング処理等を追加してください。
公式にもサンプルがあるので確認するのが良いでしょう。(今回はこちらのサンプルを参考にしています。)
4. コード生成スクリプトの実行
orval.config.ts が準備できたら、コード生成を実行します。
package.json の "scripts" に Orval を実行するコマンドを追加し、コマンドを実行しましょう
{
"scripts": {
...
"generate:api": "orval --config ./orval.config.ts"
}
}
$ npm run generate:api
実行に成功すると、今回の設定(orval.config.ts)の場合、以下のようなファイルが生成されていることがわかります。
my-next-app/
├─ apiClient/
+ │ ├─ colors/
+ │ │ ├─ colorsAPI.msw.ts
+ │ │ └─ colorsAPI.ts
+ │ ├─ schemas
+ │ │ ├─ colorListResponse.ts
+ │ │ ├─ errorResponse.ts
+ │ │ └─ index.ts
│ └─ customFetch.ts
├─ app/
├─ schemas/
│ └─ openapi.yaml
├─ orval.config.ts
├─ next.config.js
├─ package.json
└─ tsconfig.json
実際に生成されたファイルを確認してみる
今回の設定(orval.config.ts)では、colorsAPI.ts にAPIクライアントとなるコードが生成されています。
生成された colorsAPI.ts(一部抜粋)
...省略
/**
* 色を含む配列を取得します
* @summary 色の一覧を返す
*/
export const getGetApiColorsUrl = () => {
return `/api/colors`;
};
export const getApiColors = async (
options?: RequestInit
): Promise<ColorListResponse> => {
return customFetch<ColorListResponse>(getGetApiColorsUrl(), {
...options,
method: "GET",
});
};
...省略
API レスポンスとなる型や fetch を行う getApiColors の中で先ほど設定した customFetch が呼ばれているのがわかりますね。
また使用される型も以下のようにしっかり生成されています。
生成されたレスポンス型ファイル
/**
* 色の文字列の配列
*/
export type ColorListResponse = string[];
/**
* 標準的なエラーレスポンス
*/
export interface ErrorResponse {
/** エラーの詳細メッセージ */
message: string;
}
export * from './colorListResponse';
export * from './errorResponse';
そして今回は設定(orval.config.ts)にて、output.client: 'react-query' を指定しているため、それらコードも生成されています。
生成された colorsAPI.ts(一部抜粋)
...省略
import { useQuery } from "@tanstack/react-query";
import type {
QueryFunction,
QueryKey,
UseQueryOptions,
UseQueryResult,
} from "@tanstack/react-query";
...省略
/**
* @summary 色の一覧を返す
*/
export function useGetApiColors<
TData = Awaited<ReturnType<typeof getApiColors>>,
TError = ErrorResponse
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getApiColors>>,
TError,
TData
>;
request?: SecondParameter<typeof customFetch>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetApiColorsQueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
...省略
いろいろごちゃごちゃしてますが、useGetApiColors が先ほどの getApiColors を React-Query の useQuery のように使えるカスタムフックになっています。
colorsAPI.msw.ts にはMSW用のモックデータおよびハンドラーが生成されています。
生成された colorsAPI.msw.ts
import { HttpResponse, delay, http } from "msw";
import type { ColorListResponse, ErrorResponse } from "../schemas";
export const getGetApiColorsResponseMock = (): ColorListResponse => [
"red",
"green",
"blue",
];
export const getGetApiColorsMockHandler = (
overrideResponse?:
| ColorListResponse
| ((
info: Parameters<Parameters<typeof http.get>[1]>[0]
) => Promise<ColorListResponse> | ColorListResponse)
) => {
return http.get("*/api/colors", async (info) => {
await delay(1000);
return new HttpResponse(
JSON.stringify(
overrideResponse !== undefined
? typeof overrideResponse === "function"
? await overrideResponse(info)
: overrideResponse
: getGetApiColorsResponseMock()
),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
});
};
今回は最初の openapi.yaml に GET メソッドの一つしか用意していなかったので一つしか生成されていませんが、複数のメソッドおよびエンドポイントが記載されていれば、その分生成され、それらが最後の行の Handlers の関数(今回でいう getColorsAPIMock)に集約されます。
次の章でこちらの Handlers の関数を使用することで MSW サーバーに登録することができます。
MSW の導入
前の章では Orval を使用し API クライアントコードと同時に MSW 用のモックハンドラー (colorsAPI.msw.ts など)を生成しました。
この章から、生成されたモックハンドラーを活用するために、MSW(Mock Service Worker) をプロジェクトにセットアップし、開発時に API モックを有効にする手順を説明します。
とくに Next.js App Router は Pages Router に比べ、MSW のセットアップに若干の癖があり、方法によっては一部問題点*(2025/04/22現在)も存在します。それらを含めここでまとめて現在可能な導入手順を紹介します。
※ MSW そのものについては各種記事が豊富にありますのでそちらをご参照ください。
1. MSW のインストール
まず、MSW をインストールします。
$ npm install msw --save-dev
2. Service Worker スクリプトの初期化(ブラウザ環境用)
MSW をブラウザで動作させるためには、Service Worker スクリプトが必要です。
以下のコマンドを実行して、Next.js の公開ディレクトリ public/ に Service Worker スクリプト mockServiceWorker.js を生成します。
$ npx msw init public/ --save
実行後、public ディレクトリに mockServiceWorker.js ファイルが作成されていれば成功です。
my-next-app/
├─ apiClient/
├─ app/
├─ public/
+ │ └─ mockServiceWorker.js # <-- これが生成される
├─ schemas/
│ └─ openapi.yaml
├─ orval.config.ts
├─ next.config.js
├─ package.json
└─ tsconfig.json
この mockServiceWorker.js は、ブラウザ上で動作し、アプリケーションからのネットワークリクエスト(fetch など)を捕捉して MSW のモック処理に渡す役割を担います。public/ ディレクトリ配下に置くことで、Service Worker として正しく登録・動作させることができます。
3. MSW 起動に必要なファイルを作成
まず MSW 公式 Docs にもあるような、一般的な設定ファイルを作成します。
my-next-app/
├─ apiClient/
├─ app/
├─ public/
│ └─ mockServiceWorker.js
+ ├─ mocks/
+ │ ├─ browser.ts
+ │ ├─ server.ts
+ │ ├─ handlers.ts
├─ schemas/
│ └─ openapi.yaml
├─ orval.config.ts
├─ next.config.js
├─ package.json
└─ tsconfig.json
ここでは MSW の設定ファイルを格納する mocks/ ディレクトリを作成し、以下の3つの基本的なファイルを用意します。
-
handlers.ts: API のモック定義(リクエストハンドラー)を集約するファイルです。Orval が生成したハンドラーや、独自に作成したハンドラーをここにまとめます。 -
browser.ts: ブラウザ環境で MSW を動作させるための設定ファイルです。Service Worker をセットアップします。 -
server.ts: Node.js 環境で MSW を動作させるための設定ファイルです。
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
import { getColorsAPIMock } from "@/apiClient/colors/colorsAPI.msw";
import type { RequestHandler } from "msw";
// Orval で生成されたモックハンドラーをここに記述
const orvalHandlers = [...getColorsAPIMock()];
// Orval とは別で定義したモックハンドラーを追加する場合はここに記述
const originalHandlers: RequestHandler[] = [];
// モックハンドラを結合(originalHandlersを前にすることで、orvalHandlers を上書きするようにしておく)
export const handlers: RequestHandler[] = [...originalHandlers, ...orvalHandlers];
-
handlers.tsには先ほど Orval で生成されたcolorsAPI.msw.ts内にあるgetColorsAPIMockをインポートしてきて入れています。 - 本記事と異なる
openapi.yamlを使っている場合でも、生成されたOOO.msw.tsファイルの一番下の行に生成されていると思うので、各自ご確認ください。
...省略
export const getColorsAPIMock = () => [getGetApiColorsMockHandler()];
また以下例のように、Orval で生成される Handler の引数に任意の値を渡すことで、レスポンス値に書き換えることもできます。こちらも適宜活用すると良いでしょう(参考)
import { getGetApiColorsMockHandler } from "@/apiClient/colors/colorsAPI.msw";
...
const originalHandlers = [
getGetApiColorsMockHandler(["rainbow", "superRainbow", "hyperRainbow"]),
];
...
このように getGetApiColorsMockHandler のような Orval が生成した関数を呼び出す際に引数を渡すことで、特定のシナリオ(例えばテスト時)で返すモックデータを簡単に上書きすることができます。
つづいて作成した browser.ts と server.ts を実行する機構を作ります。
4. MSW Provider コンポーネントの作成
MSW を起動するためのラッパーコンポーネントを作ります。(参考)
my-next-app/
├─ apiClient/
├─ app/
├─ public/
├─ mocks/
├─ schemas/
+ ├─ components/
+ │ ├─ MswClientProvider.tsx
+ │ └─ MswProvider.tsx
├─ orval.config.ts
├─ next.config.js
├─ package.json
└─ tsconfig.json
"use client";
import { use } from "react";
// MSW ワーカーを初期化する非同期処理(Promise)
const mockingEnabledPromise =
typeof window !== "undefined"
? import("@/mocks/browser").then(async ({ worker }) => {
await worker.start({ onUnhandledRequest: "bypass" });
})
: Promise.resolve();
// MSW クライアントプロバイダー
export const MswClientProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
// useでPromise(MSW の初期化完了)を待機 -> Promise が解決後子要素をレンダリング
use(mockingEnabledPromise);
return children;
};
-
MswClientProvider.tsxはクライアントコンポーネントから叩かれる API をモックするために用意します。 -
mockingEnabledPromise内で、先ほどbrowser.tsに設定した MSW のworkerを起動しています。worker起動後にコンポーネントを描画したいので、React の use を使っています。
import { Suspense, type ReactNode } from "react";
import { MswClientProvider } from "./MswClientProvider";
if (process.env.NEXT_RUNTIME === "nodejs") {
const { server } = await import("@/mocks/server");
server.listen({ onUnhandledRequest: "bypass" });
}
// 使用するProviderコンポーネント
export const MswProvider = ({ children }: { children: ReactNode }) => {
return (
<Suspense fallback={null}>
<MswClientProvider>{children}</MswClientProvider>
</Suspense>
);
};
-
MswProviderはサーバーコンポーネントから叩かれる API をモックするために用意します。 - コンポーネント内ではなく、そのまま外に
server.tsで設定した MSW の処理を実行させています。 - また先ほどの
MswClientProviderを呼び出す親コンポーネントでもあり、MswClientProviderの処理が終わるまではSuspenseで描画を待ちます。
定義した MSWProvider を layout.tsx などの API を使用する親コンポーネントで呼び出し、ラップします。
import "./globals.css";
import { MswProvider } from "@/components/MswProvider";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ja">
<body>
+ <MswProvider>{children}</MswProvider>
</body>
</html>
);
}
今回はアプリケーションのルートレイアウトである layout.tsx で {children} を <MSWProvider> でラップすることにより、全てのページ・コンポーネントがレンダリングされる前に MSW が起動されるようになります。
5. MSW の起動
コードが書けたらアプリケーションを実行し、ブラウザのコンソールに赤文字で「[MSW] Mocking enabled.」が出れば起動成功です!
npm run dev

別の MSW 設定方法
余談ですが、Next.js の Instrumentation を使用する方法(現地点では一部問題点あり)も紹介します。
興味のある方はお読みください。追加で設定する必要はありません。
別の MSW 設定方法
サーバーコンポーネントから叩かれる API をモックするために Next.js の Instrumentation を使用する方法があります。
my-next-app/
├─ apiClient/
├─ app/
├─ public/
├─ mocks/
├─ schemas/
├─ components/
+ ├─ instrumentation.ts
├─ orval.config.ts
├─ next.config.js
├─ package.json
└─ tsconfig.json
export async function register() {
// "msw/node"がNode.jsランタイムでのみ利用可能(=Edgeランタイムで利用不可)
if (process.env.NEXT_RUNTIME === "nodejs") {
const { server } = await import("@/mocks/server");
server.listen();
}
}
参考: add next.js app router example #101
こちらの instrumentation.ts の処理は Next.js のサーバインスタンスが起動された際に一度だけ実行されます。
これによりサーバーコンポーネントから叩かれる API 処理もモックすることが可能です。
そのためアプリケーション実行中にコード編集をするとうまく MSW によるモックが機能しなくなり、再度実行 npm run dev等 をし直す必要があります。
現在も対応が行われているようですが、未だ特定の解決法はないようです。
ですので、個人的には前で紹介している MswProvider のようなラッパーコンポーネントを組み込む方法が良いかと思います。
もし根本から解決するのであれば、本アプリのフロントサーバーとは別に API モックサーバーを立て、環境変数でリクエスト先の本番 API サーバー/ API モックサーバーを切り替えるのも良いでしょう。
別で環境を構築しても良いですが、Vercel が提供する Turborepo を用いて、本アプリのフロントサーバーと API モックサーバーを立てると楽に構築できそうです。
他にも良い方法があればぜひコメントで教えてください!!
使用例
ではいよいよ環境は整ったので、Orval で生成した関数を用いて API を叩くコンポーネントを作ってみましょう。
また今回は TanStack Query(React Query)も使うので、ここらでインストールしておきます。
$ npm install @tanstack/react-query
それではサーバーコンポーネントのサンプル SampleServer.tsx と、クライアントコンポーネントのサンプル SampleClient.tsx をつくり、それをトップページのファイル app/page.tsx で呼び出します。
my-next-app/
├─ apiClient/
├─ app/
+ │ └─ page.tsx
├─ public/
├─ mocks/
├─ schemas/
├─ components/
│ ├─ MswClientProvider.tsx
│ ├─ MswProvider.tsx
+ │ ├─ SampleSever.tsx
+ │ └─ SampleClient.tsx
├─ instrumentation.ts
├─ orval.config.ts
├─ next.config.js
├─ package.json
└─ tsconfig.json
import SampleClient from "@/components/SampleClient";
import SampleServer from "@/components/SampleServer";
const Page = () => {
return (
<>
<SampleServer />
<SampleClient />
</>
);
};
export default Page;
import { getApiColors } from "@/apiClient/colors/colorsAPI";
import { Suspense } from "react";
const Body: React.FC = async () => {
const response = await getApiColors();
return (
<>
<h2>Server Component</h2>
{response.map((color, i) => (
<p key={i}>{color}</p>
))}
</>
);
};
const SampleServer: React.FC = async () => {
return (
<Suspense fallback={<div>SampleServer Loading...</div>}>
<Body />
</Suspense>
);
};
export default SampleServer;
-
SampleServerコンポーネントは、'use client' ディレクティブがないため、デフォルトで サーバーコンポーネントとして動作します。 - 内部の
Bodyコンポーネントで、Orval が生成したgetApiColors関数を直接awaitしてデータを取得しています。 - データの取得が完了するまでの間は、
SuspenseのfallbackのSampleServer Loading...が表示されます。
"use client";
import { useGetApiColors } from "@/apiClient/colors/colorsAPI";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const Body: React.FC = () => {
const { data, isPending, error, isError } = useGetApiColors();
if (isError) {
return <p>{error.message}</p>;
}
if (isPending) {
return <p>SampleClient Loading...</p>;
}
return (
<>
<h2>Client Component</h2>
{data.map((color, i) => (
<p key={i}>{color}</p>
))}
</>
);
};
const SampleClient: React.FC = () => {
// TanStack Queryを使用するためのQueryClientを作成し、QueryClientProviderでラップ
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>
<Body />
</QueryClientProvider>
);
};
export default SampleClient;
-
SampleClientコンポーネントは、'use client' ディレクティブが付与された クライアントコンポーネント です。 - 内部の
Bodyコンポーネントで、Orval が生成した React Query 用のカスタムフックuseGetApiColorsを使用してデータを取得しています。 - TanStack Query(
QueryClientProvider,useGetApiColorsの中のuseQuery) が、ローディング状態(isPending)、エラー状態(isError,error)、成功時のデータ(data)の管理を行ってくれます。
動作確認
それでは起動して動作確認してみます
$ npm run dev

しっかりと表示の確認ができました!
今回は openapi.yaml の example に ["red", "green", "blue"] と書いていて、かつ orval.config.ts で useExamples: true としていたので、その値が返ってきていますね。
再掲「schemas/openapi.yaml」「orval.config.ts」
...
# 成功レスポンス(200)のスキーマ
ColorListResponse:
description: 色の文字列の配列
type: array
items:
type: string
example: ["red", "green", "blue"] # スキーマレベルで Example を定義
...
...
export default defineConfig({
color_api: {
output: {
mock: {
type: "msw",
useExamples: true, // openapi.yamlのexampleをmockデータとして使用するか(ない場合はfaker.jsでmockデータが生成される)
},
},
},
});
「3. MSW 起動に必要なファイルを作成」の章でも話しましたが、モックのレスポンス値を変えたい場合は handler.ts の originalHandlers に任意の MSW の Handler を追加すると良いでしょう。
再掲「mocks/handler.ts」
import { getGetApiColorsMockHandler } from "@/apiClient/colors/colorsAPI.msw";
...
const originalHandlers = [
+ getGetApiColorsMockHandler(["rainbow", "superRainbow", "hyperRainbow"]),
];
...
また序盤にも戻りますが、orval.config.ts の設定によってもモックのレスポンスを変えられます。
再掲「orval.config.ts」
export default defineConfig({
color_api: {
output: {
mock: {
type: "msw",
useExamples: true, // openapi.yamlのexampleをmockデータとして使用するか(falseの場合 や example が無い場合はfaker.jsでmockデータが生成される)
generateEachHttpStatus: true, // 200以外の各HTTPステータスコードのmockを生成するか
},
override: {
mock: {
required: true, // 自動生成で返却されるmockデータを必須にするか
},
},
},
...
},
});
ぜひ色々設定をいじって確かめてみてください!
まとめ
本記事では、Next.js(App Router)環境に、Orval と MSW を導入し、API クライアントコードの実装とモック環境の構築例を紹介しました。
Orval と MSW を用いることで比較的簡単に、
- 型安全な API クライアントコードの自動生成
- API スキーマと一貫性のあるモック環境の構築
が可能になります。
ぜひご自身のプロジェクトでも導入を検討してみてください。
今回紹介した手順を参考になれば幸いです!
Discussion
こんにちは、素晴らしい記事をありがとうございます。
記事内にはコード例があり非常に参考になりましたが、GitHubなどに上がっている完全なサンプルプロジェクトは見当たりませんでした。
もし可能であれば、GitHubリポジトリやフルサンプルを共有していただく予定はありますでしょうか?
ご検討いただけますと幸いです!
CodeSandBoxにて公開しました。こちらのURLからご確認ください! また実際の挙動を確認する際はfork等してください。
素晴らしい記事ありがとうございます!!
QueryClientProviderを外部化して、layoutラップでも無事、client fetch, server featchがモック経由できました。
これはかなり実用的ですね!助かりました。