はじめに
REST API を叩くフロントエンドを書いていると、こんな悩みはありませんか?
- バックエンドの API がまだできておらず、フロントの実装や画面確認が進まない
- Swagger(OpenAPI)の定義と実際のレスポンスがズレていて、型がすぐ壊れる
- テストを書くたびに
fetchやaxiosを毎回モックするのが面倒 - ステージング環境のデータが不安定で、動作確認がつらい
この記事では、こういった「API まわりのつらさ」をまとめて解決するために、
TypeScript + API スキーマ(OpenAPI) + モックツール(MSW)
という構成で、API クライアントとモックサーバーをセットで整える方法を解説します。
ツール名は多少マニアックですが、やっていることはシンプルで、
「API 仕様を 1 つ決めて、そこから型・クライアント・モックを全部生やす」
という発想です。
この記事のゴール
この記事を読み終えると、次のことができるようになります。
- バックエンドが未完成でも、TypeScript で 型安全に API 通信を書くイメージ が持てる
- API 仕様(OpenAPI)を元に、API クライアントとモックサーバーをセットで用意する流れ を説明できる
- 小さなサンプルコードを元に、自分のプロジェクトにどんな形で導入するかをイメージできる
一言でいうと:API 定義を「単一の真実」にして、そこから全部生やす
この構成の肝は、
API 仕様(OpenAPI)を「単一の真実(Single Source of Truth)」にする
という考え方です。
-
API 仕様(OpenAPI)
- 「どんな URL に」「どんなパラメータで」「どんな JSON が返るか」を書いた設計書
- 通常は
openapi.yaml/openapi.jsonとして管理
-
OpenAPI Generator(や類似ツール)
- この設計書を読み込み、TypeScript の 型定義や API クライアント関数 を自動生成するツール
-
MSW(Mock Service Worker)
- ブラウザや Node.js のネットワークレベルでリクエストを横取りして、モックレスポンスを返してくれるライブラリ
-
fetchやaxiosを置き換えずに、「本物と同じコードパス」でモック動作ができる
各ツールの役割をざっくり整理
OpenAPI(API 仕様書)
- YAML / JSON で書く API の設計書
- 「どの URL に」「どの HTTP メソッドで」「どんなリクエスト/レスポンスか」を定義
OpenAPI Generator(TypeScript クライアントの自動生成)
npx @openapitools/openapi-generator-cli generate \
-i openapi.yaml \
-g typescript-fetch \
-o src/generated
生成後、src/generated 配下に型定義とクライアントが出力されます。
MSW(Mock Service Worker)
- リクエストを横取りし、モックレスポンスを返す
- ブラウザでも Node.js でも動作する
- テストと開発どちらでも共通のモックを再利用できる
この構成が「すごい」理由 5つ
1. API 定義から型とクライアントが自動で生えてくる
手書きの API コードで起こりがちなミスを削減し、
仕様 → 生成 → コンパイルエラーで差分検知 ができるようになります。
2. クライアントとモックの型が完全一致する
import { User } from '../../generated'
const users: User[] = [
{ id: 1, name: 'Alice' }
]
型のズレを TypeScript が検知してくれるので、
**「本番とモックの JSON が違う地獄」**を防げます。
3. バックエンド未完成でもフロント開発が進められる
OpenAPI さえあれば、
- クライアント生成
- モック作成
- UI 実装
- エラーハンドリングの検証
まで、バックエンドの完成を待つ必要がありません。
4. テストで「本番とほぼ同じコードパス」を通せる
-
jest.mockのように関数を差し替えない - ネットワーク層でモックするため、
アプリ本体は本番と同じように API を叩いている つもりで動く
これは回帰バグを非常に減らします。
5. 変更に強く、長期運用で効いてくる
OpenAPI を変えれば:
- クライアント再生成
- TypeScript のコンパイル
- 壊れている場所がわかる
という流れができるため、
変更に強い API 周りのコードが出来上がります。
ミニマムなサンプル構成
1. OpenAPI 定義
openapi: 3.0.0
paths:
/users:
get:
operationId: listUsers
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
required: [id, name]
properties:
id:
type: integer
name:
type: string
2. OpenAPI Generator の実行
npx openapi-generator-cli generate \
-i openapi.yaml \
-g typescript-fetch \
-o src/generated
3. 生成クライアントをラップして使う
import { DefaultApi, Configuration } from '../generated'
const api = new DefaultApi(new Configuration({ basePath: '/api' }))
export const fetchUsers = async () => {
return api.listUsers()
}
4. MSW の型安全なハンドラ
import { rest } from 'msw'
import { User } from '../../generated'
export const userHandlers = [
rest.get('/api/users', (_req, res, ctx) => {
const users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]
return res(ctx.status(200), ctx.json(users))
}),
]
5. ブラウザとテストのエントリ
// browser.ts
import { setupWorker } from 'msw'
import { userHandlers } from './handlers/users'
export const worker = setupWorker(...userHandlers)
// server.ts
import { setupServer } from 'msw/node'
import { userHandlers } from './handlers/users'
export const server = setupServer(...userHandlers)
例え話:マンション建設で考える
- OpenAPI → 建築図面
- 生成クライアント → 自動で加工された部材
- MSW モック → 実物大のモデルルーム
図面を変えれば、部材もモデルルームも更新される。
ズレが起きない開発フロー がこの構成の魅力です。
よくある失敗パターン
- OpenAPI が後追いでメンテされる
- 生成コードに手を加えてしまう
- モックと仕様が別管理でズレる
- 初期導入で全部盛りにして破綻する
→ 対策は本文中で詳しく説明しています。
導入ステップ
- API 仕様の責任者を決める
- まずは 1 エンドポイントだけ OpenAPI 化
- クライアント生成スクリプトを作成
- ラッパー API レイヤーを作る
- MSW モックを型安全に作成
- 開発・テストに組み込んで回す
まとめ
- API 仕様から 型・クライアント・モック を一元生成すると、
バックエンド未完成でもフロントが進む
仕様ズレが起きにくい
本番に近いテストが書ける
という大きなメリットがあります。
まずは 1 画面だけ、この構成を試してみてください。