フロントエンド・API連携

MSW(Mock Service Worker)によるAPIモック化:フロントエンド開発をバックエンドから独立させる手順

バックエンドの実装待ちで、フロントエンドが丸ごと止まる。Laravel で API を組んでいる案件なら、誰もが一度は経験する古典的なボトルネックだ。画面側は大方できているのに、レスポンス形式が二転三転し、そのたびにフロントエンドまで修正が波及する。WordPress の案件でも、ACF のフィールド設計が固まる前にフロントだけ先行せざるを得ない状況がある。…

MSW(Mock Service Worker)によるAPIモック化:フロントエンド開発をバックエンドから独立させる手順

こういうとき、従来であれば json-server のような別プロセスのモックサーバーを立てるか、ソース側に if (development) の分岐を散りばめてお茶を濁すことになる。どちらも短期的には動く。しかし、API の数が増え、認証やエラー処理まで必要になると、モックのための設定と本番用の実装が別々に育ち始める。最終的には、モックが実際の API と似ているのかどうかさえ分からなくなる。

そこで使えるのが MSW(Mock Service Worker)だ。ブラウザの Service Worker を利用してネットワークリクエストを横取りし、API から返ってきたように見えるレスポンスを返す。導入は npm install msw --save-dev から始まり、v1 と v2 の API の違いを押さえれば、現場で十分に実用へ持ち込める。

結論から言えば、MSW は「力技だが頼れる」道具だ。フロントエンドのコンポーネントにモック専用の分岐を持たせず、ネットワーク層でモックを差し替えられる。これによって、フロントエンドとバックエンドをある程度独立して進められるようになる。

ただし、運用上の線引きは必要だ。「開発環境だけで動かす」「本番ビルドにモックを混入させない」というルールを最初に決めておかないと、リリース後に偽データが流れる事故につながる。本稿では、MSW v2 のセットアップからハンドラーの定義、開発環境に限定した有効化、REST と GraphQL をまたぐモック設計まで、実装時に迷いやすい箇所を順に見ていく。

MSWがフロントエンド開発のボトルネックを解消する仕組み

MSW の動作原理を一言で言えば、ブラウザに登録した Service Worker を使ってネットワークリクエストを横取りし、条件に合うハンドラーから別のレスポンスを返す仕組みだ。

アプリケーションのコンポーネントが fetch や Axios で API を呼び出すところは変わらない。呼び出し先も、基本的には実際に接続する API と同じ URL にする。開発時だけ Service Worker が間に入り、指定したリクエストに対してモックレスポンスを返す。本番環境では MSW を起動しなければ、通常どおり実際の API に接続する。

この「呼び出し側を変えない」ことが大きい。モックのためにコンポーネントへ専用のデータ取得処理を追加すると、画面のロジックと開発用の都合が混ざる。テスト用の固定データを直接読み込む構成も、画面が増えるほど管理が難しくなる。MSW なら、コンポーネントは /api/users に GET を送るだけでよい。返ってくるデータがモックか本物かは、ネットワーク層の外側で切り替わる。

従来の方法と比べると、違いは次のようになる。

手法仕組み向いている場面注意点
ソース内の条件分岐開発時だけ固定データを返す小さな画面の仮実装本番コードに分岐が残りやすい
json-server など別プロセスで簡易 API を起動するAPI 形式を手早く試すポート、CORS、認証を別途管理する必要がある
Mirage.jsブラウザ内で API モックを実行する JavaScript ライブラリクライアント側にデータ層をまとめたい場合独自のモデルやルート定義を学ぶ必要がある
MSWService Worker や Node のリクエスト層で処理する実際の通信処理を保ったままモックしたい場合Service Worker の動作条件と登録状態に注意する

Mirage.js は、ブラウザ内で API モックを実行する JavaScript ライブラリだ。MSW と同じくフロントエンド開発をバックエンドから切り離す用途で使えるが、アプリケーション内にモデルやルートを構成していく考え方が強い。一方、MSW は「リクエストを受けて、条件に応じたレスポンスを返す」というネットワーク寄りの設計になる。ここは似た用途のツールでも、触ったときの感覚がかなり違うところだ。

MSW が特に効くのは、Axios や fetch で書かれた既存コードに手を加えずに済む点だ。たとえば、フロントエンド側が /wp-json/wp/v2/posts を呼び出しているなら、開発環境ではそのリクエストを MSW が捕捉し、WordPress の REST API に近いレスポンスを返せる。Laravel 側のエンドポイントを呼ぶ場合も同じで、コントローラーが完成する前に画面の状態を確認できる。

これは単に「画面を表示できる」という話ではない。ローディング中の表示、空の配列が返った場合、認証エラー、入力エラー、サーバーエラーといった、バックエンドの実装が終わらないと確認しづらい状態も先に作れる。UI の実装を API 完成後にまとめて始めるより、画面の仕様に抜けが見つかりやすい。

コンポーネントは、データソースがモックか本番かを知らなくていい。切り替えの責任をネットワーク層に寄せることで、画面のコードが余計な事情を背負わずに済む。

もちろん、MSW を入れればすべてが自動的に解決するわけではない。Service Worker は HTTPS または localhost 配下で動作する。file:// で開いた静的ファイルや、単純に IP アドレスを直接指定した検証環境では、そのまま登録できないことがある。

Docker でフロントエンドを動かしている場合も注意が必要だ。ブラウザから見た localhost と、コンテナ内部から見た localhost は同じではない。Service Worker の登録対象はブラウザがアクセスしているページのオリジンに依存するため、コンテナ間通信の感覚だけで設定すると切り分けに失敗する。まずブラウザで開いている URL が HTTPS または localhost になっているか、mockServiceWorker.js がそのオリジンから取得できるかを確認したい。

また、Service Worker の登録範囲も見落としやすい。通常は公開ディレクトリに配置した mockServiceWorker.js をアプリケーションのルートから配信するが、サブパス配下にデプロイする構成では、ファイルの場所とスコープがずれることがある。開発サーバーでは動くのに、プレビュー環境では動かない場合、ハンドラーの記述より先にこの登録状態を疑うべきだ。

MSW v2のセットアップとService Workerの初期化手順

MSW v1 から v2 への移行では、リクエストハンドラーの API が大きく変わった。v1 で広く使われていた rest.get()res(ctx.json(...)) は、v2 では基本的に http.get()HttpResponse.json() を使う。

古い記事や社内の過去プロジェクトをそのまま参照すると、インストールはできたのにハンドラーだけ動かない、という状態になりやすい。MSW の導入で最初につまずくのは、設定の複雑さよりも、この世代差であることが多い。

まず開発用依存関係として MSW を追加する。

npm install msw --save-dev

続いて、ブラウザ用の Service Worker ファイルを生成する。

npx msw init public --save

ここで指定している public は、アプリケーションの構成に合わせて読み替える。Vite や多くのフロントエンド環境では、公開ファイルを配置するディレクトリとして public を使う。生成される mockServiceWorker.js は、MSW のハンドラーそのものではない。ブラウザ上でリクエストを受け取り、アプリケーション側で定義したハンドラーへ処理をつなぐための Service Worker だ。

--save を付けて初期化すると、package.json に Service Worker の配置先が記録される。これによって、別のメンバーがリポジトリを取得したときも、同じディレクトリを前提に初期化できる。

プロジェクトに組み込む手順は、次の順番にしておくと分かりやすい。

1. MSW を開発用依存関係として追加する。

2. msw init で Service Worker ファイルを生成する。

3. ハンドラーをまとめるファイルを作る。

4. ブラウザ用の setupWorker を初期化する。

5. 開発環境のエントリーポイントから worker.start() を呼び出す。

6. ブラウザの開発者ツールで Service Worker の登録状態を確認する。

たとえば、ハンドラーを src/mocks/handlers.ts にまとめ、ブラウザ用の初期化処理を src/mocks/browser.ts に置く構成は扱いやすい。ハンドラーと起動処理を分けておけば、後から Node 環境のテストで同じハンドラーを再利用しやすい。

browser.ts では setupWorker(...handlers) を呼び出して Worker を作る。重要なのは、ここで作った Worker をどこからでも自由に起動しないことだ。アプリケーションの起動処理に近い場所で、開発環境であることを確認したうえで一度だけ開始する。複数のコンポーネントから worker.start() を呼ぶ構成にすると、初期化順序が分かりにくくなり、警告や登録競合の原因になる。

Service Worker ファイルを Git に含めるかどうかは、チームの運用で決めればよい。ただし、含めない場合は、初期セットアップを誰かの手作業に任せないことが重要だ。CI で依存関係をインストールした直後に npx msw init public --save を実行する、あるいは package.json のスクリプトに初期化処理を組み込む。どちらを選んでも、クローンしただけでは動かない状態を放置しないほうがいい。

ここで確認したいのは、Service Worker ファイルが存在することだけではない。ブラウザからそのファイルを直接開けること、ページと同じオリジンから取得できること、開発者ツールの Application あるいは Service Workers の画面で登録済みになっていることまでを見る。ハンドラーが正しいのに API がモックされない場合、まず Worker の登録を確認するのが近道だ。

httpオブジェクトによるAPIハンドラーの定義とレスポンス生成

MSW v2 のハンドラーは、httpHttpResponse を中心に定義する。

import { http, HttpResponse } from 'msw'

たとえば、ユーザー一覧を返す GET ハンドラーなら、http.get('/api/users', () => HttpResponse.json([...])) という形になる。実際には、固定データをハンドラーの中へ直接詰め込むより、fixture やファクトリー関数へ分けておくほうが後から変更しやすい。

ハンドラーで意識する点は、次の三つだ。

  • リクエストの種類と URL は http.gethttp.post などで明示する。
  • 正常系だけでなく、必要なステータスコードやレスポンスヘッダーも再現する。
  • モックデータの形を、フロントエンドが実際に依存する API 契約に合わせる。

レスポンスの本文だけなら HttpResponse.json() で十分だ。作成処理の結果として 201 を返したい、認証エラーとして 401 を返したい、といった場合は、レスポンスのオプションでステータスを指定する。ヘッダーが画面の挙動に関わる場合も、同じ場所で設定できる。

POST や PUT では、ハンドラーのコールバックから request を受け取り、ボディを読み出す。入力値をそのまま返すだけのモックにしておけば、フォーム送信後の画面遷移や、保存後の再取得を確認しやすい。ただし、すべての入力を無条件に受け入れると、実際の API では失敗する値まで成功してしまう。画面側のバリデーションを確認したいなら、条件に応じて 422 を返すケースも用意する。

動的パスは、URL に :id のようなパラメーターを置いて受け取る。

http.get('/api/users/:id', ({ params }) => ...)

この場合、params.id から URL に含まれた識別子を取得できる。存在しない ID に対して 404 を返すハンドラーを作っておけば、詳細画面のエラー表示もバックエンドの完成を待たずに確認できる。

クエリパラメーターは request.url を URL オブジェクトとして扱う。検索画面なら、filter、ページ番号、並び順などを取り出して、条件によって返すデータを変える。ここで大切なのは、実際の API と同じパラメーター名を使うことだ。モックだけが keyword、本番 API が search という状態になると、モック上で動いている画面が本番接続時に壊れる。

認証ヘッダーも同様に確認できる。request.headers.get('Authorization') で値を取り出し、存在しない場合は 401 を返す、といった設計にできる。認証そのものを完全に再現する必要はないが、ログイン済みと未ログインで画面が変わるアプリケーションなら、少なくとも状態を切り替えられるようにしておきたい。

レスポンスを一つの固定データだけにするより、状態を生成する関数を用意すると扱いやすい。たとえばユーザー一覧を複数画面で利用するなら、一覧用、詳細用、空状態用のデータを同じデータ構造から組み立てる。そうしておけば、フィールド名を変更したときに、あちこちのハンドラーを手で直す必要がない。

一方で、モックの内部に本番バックエンドと同じ処理を再現し始めると、別のバックエンドを作ることになる。認可ロジック、複雑な集計、データベースの整合性までブラウザ内で再現する必要はない。フロントエンドが確認したい契約と状態に絞り、サーバー側の責任まで抱え込まないのが現実的な線引きだ。

MSW のハンドラーは、バックエンドのコピーではない。画面が必要とする通信契約と状態を、フロントエンドから検証できる形にするための境界だ。

開発環境のみでモックを有効化する安全な実装パターン

ここが MSW を仕事で使えるかどうかを分けるポイントになる。

開発環境でモックを動かし、本番ビルドでは確実に無効化する。この線引きを最初に決めておかないと、リリース後に固定データが表示される事故が起きる。ユーザー一覧が常に同じ内容になる程度なら発見できるかもしれないが、認証情報や内部向けの fixture が配信されれば、単なる表示崩れでは済まない。

最も素直な実装は、モックを有効化する関数を一つに集約することだ。開発環境であることを判定し、ブラウザで実行されている場合だけ worker.start() を呼び出す。Vite なら import.meta.env.DEV、一般的な Node ベースの構成なら環境変数を利用する。Next.js や Nuxt のようにサーバー側でもコードが実行される環境では、Service Worker を使えるのがブラウザだけであることにも注意が必要だ。

実装上は、次のような役割分担にする。

  • ハンドラー定義は handlers に集約する。
  • ブラウザ用の Worker は setupWorker(...handlers) で作る。
  • 開発環境の起動処理だけが worker.start() を呼ぶ。
  • 本番用のエントリーポイントからは、モックの起動処理を経由させない。
  • テスト環境では、必要に応じて setupServer を使い、同じハンドラーを再利用する。

ここで避けたいのは、各コンポーネントが個別にモックを開始する構成だ。ページごとに「この画面ではモックを使う」「この画面では本番 API を使う」といった判断を持たせると、動作環境の組み合わせが増える。アプリケーション全体の起動時に一度だけ判断し、画面側にはその事情を見せないほうが保守しやすい。

開発環境の判定も、単純な文字列比較を何か所にも書かないほうがいい。たとえば、あるファイルでは NODE_ENV、別のファイルでは VITE_USE_MOCK、さらに別の場所では独自の環境変数を参照する、という状態になると、モックの有効・無効が設定を見ただけでは分からなくなる。起動条件は一つの関数、または一つの設定値に寄せるべきだ。

ステージングやプレビュー環境でモックを使いたい場合は、さらに慎重になる必要がある。環境名が本番と異なるから安全とは限らない。実際に公開される URL であれば、モックデータの露出や Service Worker の登録が問題になる可能性がある。プレビュー環境でも使うなら、認証や公開範囲を含めて、どのデータを返してよいかを決めておく。

本番ビルドへの混入を防ぐため、CI に最低限の検査を入れておくのも有効だ。たとえば、ビルド成果物に worker.start が残っていないか、モック用の fixture が意図せず含まれていないかを確認する。プロジェクトの規模によっては、単純な文字列検索でも出発点として役に立つ。より厳密にするなら、本番用のビルド設定でモック関連の import 自体を含めない構成にする。

ただし、mockServiceWorker.js が公開ディレクトリに存在することと、モックが有効であることは別の話だ。Service Worker のファイルが配信されていても、アプリケーションが worker.start() を呼ばなければリクエストは横取りされない。それでも本番に不要なファイルを置きたくないなら、本番用の公開ディレクトリから除外するほうが分かりやすい。

Service Worker を停止したあとも、ブラウザに登録情報が残っていることがある。コードから起動処理を削除したのに挙動が変わらない場合は、開発者ツールから登録済みの Service Worker を解除し、キャッシュも確認する。MSW の不具合に見えて、実際には古い Worker が動いているだけというケースは珍しくない。

RESTとGraphQLの両方に対応するモック設計のポイント

MSW は REST API だけでなく、GraphQL のリクエストもモックできる。REST の場合は http.gethttp.post を使うが、GraphQL では graphql.querygraphql.mutation を使ってオペレーション単位でハンドラーを定義する。

GraphQL のモックでは、単に /graphql という URL に対してレスポンスを返すのではなく、クエリのオペレーション名や変数をもとに処理を分けられる。たとえば GetUser というクエリ名に対して、variables.id に応じたユーザーを返す、といった設計だ。

この違いは大きい。同じ /graphql に複数のクエリが送られる構成でも、すべてを一つの固定レスポンスで返す必要がない。Apollo、urql、Relay など、クライアント側のライブラリが変わっても、通信の契約が合っていればモック層を再利用できる。

REST と GraphQL を同じプロジェクトで扱う場合は、ハンドラーを一つの巨大な配列に詰め込まないほうがよい。たとえば、次のように領域別に分けると見通しがよくなる。

  • ユーザーや認証に関するハンドラー
  • WordPress REST API に関するハンドラー
  • Laravel の業務 API に関するハンドラー
  • GraphQL のクエリとミューテーションに関するハンドラー
  • エラーや遅延を再現するためのハンドラー

そのうえで、開発用の handlers から必要なものをまとめて読み込む。画面ごとに使うハンドラーを切り替えたい場合も、条件分岐を各コンポーネントに置くのではなく、モックの構成ファイル側で管理する。

設計面で詰まりやすいのは、どこまでモックするかの線引きだ。現場では、次の三段階に分けて考えると進めやすい。

  • 正常系だけを用意する

まず画面を表示し、データ取得後のレイアウトやコンポーネントの連携を確認する段階。最初からすべての例外を再現しようとせず、画面の骨格を固める。

  • 主要な異常系を追加する

422、401、403、404、500 など、画面上で表示を分ける必要がある状態を用意する。フォームや権限管理がある場合は、正常系と同じくらい重要になる。

  • 遅延や通信失敗を再現する

ローディング表示、再試行ボタン、タイムアウト時のメッセージを確認する。常に即時レスポンスが返るモックだけでは、実際の利用時に起こる問題を見落としやすい。

異常系は、正常系のハンドラーを上書きする形で一時的に差し替えることもできる。画面の確認が終わったら元へ戻せるため、すべての状態を常時有効にしておく必要はない。開発用のデバッグメニューや URL パラメーターで状態を切り替える方法もあるが、公開環境でその操作ができないように管理する必要がある。

Laravel と並行して開発する場合、最初は正常系のレスポンスを作り、フロントエンドの構造が固まった段階で異常系を追加する流れが扱いやすい。最初からバックエンドのエラーレスポンスを細部まで再現しようとすると、仕様変更のたびにモックも修正することになる。

ここで、API のレスポンス契約を曖昧にしないことが重要になる。モックデータの id が数値なのか文字列なのか、日時がどの形式なのか、一覧のページ情報がどこに入るのか。フロントエンドが依存する部分は、実際の API と同じ形にする必要がある。モックを簡略化してよいのは、画面に不要な内部フィールドや、バックエンド固有の実装詳細だ。

WordPress の REST API と組み合わせる場合は、ACF のフィールド構造と API のレスポンス形式が一致しないことがある。フロントエンドが必要とするプロパティだけを返す簡易モックにする方法もあるが、公開時に本番レスポンスとの差分が大きくなりすぎると危険だ。

たとえば、記事一覧を表示する画面でタイトル、スラッグ、アイキャッチ画像、カスタムフィールドを使うなら、そのプロパティは実際のレスポンスに近い形で持たせる。一方で、画面が使っていないメタ情報や埋め込みデータまで再現する必要はない。fields パラメーターや embed の挙動を完全に再現しようとすると、モックの保守コストだけが増えていく。

モックは、API 仕様書の代わりにもならない。Laravel 側のリソース定義や WordPress 側の REST レスポンスが変更された場合は、モックも同時に更新する必要がある。理想を言えば、API の型定義やスキーマを共有できるとよい。しかし、そこまで整備できない案件でも、ハンドラーを API 契約の具体例として扱い、変更時にレビュー対象へ含めるだけで効果はある。

MSWのメリットとデメリットをどう見るか

MSW のメリットは、単にモックサーバーを別で立てなくてよいことではない。フロントエンドが実際に使う通信処理を、そのままの形で検証できる点にある。

コンポーネントから見れば、開発環境でも本番環境でも同じ API クライアントを呼ぶ。モックデータを読み込むための特別な分岐や、画面専用のサービス層を追加せずに済む。テストでも同じハンドラーを利用できるため、開発時だけ別の仕様で動く状態を減らせる。

もう一つの利点は、異常系を意図的に作りやすいことだ。実際のバックエンドで 500 を発生させるのは簡単ではない。認証の期限切れやネットワーク切断も、毎回同じ条件で再現するのは難しい。MSW なら、特定のリクエストに対して決めたレスポンスを返せるので、画面側の実装を落ち着いて確認できる。

一方で、デメリットもある。

第一に、Service Worker の状態を理解する必要がある。ファイルが正しい場所にない、登録スコープが合っていない、古い Worker が残っている、という問題は、通常の JavaScript のエラーとは違う場所で起きる。チーム内にブラウザの仕組みへ慣れていないメンバーが多い場合、導入時の説明は省かないほうがよい。

第二に、モックが本番 API から離れていく危険がある。ハンドラーを書いた時点では正しいレスポンスでも、Laravel や WordPress 側の仕様変更を反映しなければ、フロントエンドは間違った前提のまま開発できてしまう。MSW は API 契約を自動的に保証する仕組みではない。型やスキーマ、レビュー、結合確認など、別の仕組みと組み合わせる必要がある。

第三に、モックを作り込みすぎると、モック自体がメンテナンス対象になる。データベースのような状態管理、複雑な権限、細かな業務ロジックをすべてブラウザ側に再現すると、モックの変更だけで時間を使うことになる。MSW の役割は、現実の API を完全に再現することではなく、フロントエンドが必要とする通信の状態を再現することだ。

判断に迷ったときは、次のように考えるとよい。

  • API の完成を待つ時間が長く、画面開発が頻繁に止まっているなら導入効果が大きい。
  • 画面が少なく、バックエンドもすぐ完成するなら、最小限の固定データで済ませてもよい。
  • API の仕様変更が多い案件では、モックの更新担当と確認方法を決めておく。
  • 画面のエラー状態やローディング状態が重要なら、MSW の利点を活かしやすい。
  • 外部 API の認証や決済など、ブラウザから完全に再現できない領域は、モックの範囲を限定する。

開発の流れにMSWを組み込む

MSW を導入するとき、最初からプロジェクト全体の API をモックしようとしないほうがいい。まず、現在作っている画面に必要な一つのエンドポイントから始める。ユーザー一覧や記事詳細など、通信内容と画面の関係が分かりやすいものを選ぶと、導入の効果を確認しやすい。

次に、正常系の表示を整える。ここではデータの量を増やすことより、レスポンスの形を本番 API に合わせることを優先する。配列、ページ情報、ネストしたオブジェクト、空の値など、コンポーネントが実際に扱う構造を確認する。

画面が表示できたら、異常系を足す。404 の詳細画面、422 のフォーム、401 のログイン切れ、500 の再試行表示というように、ユーザーの操作と結びついた状態から作っていく。単にステータスコードを変えるだけでなく、画面が何を表示し、どの操作を次に提供するのかまで確認する。

その後、実際の Laravel や WordPress の API へ接続して差分を見る。ここでモックと本番の違いが見つかったら、どちらが正しいかを決める。モックを本番へ合わせる場合もあれば、フロントエンドの期待値を修正する場合もある。MSW を使っているからこそ、この比較をバックエンドの完成前から始められる。

Docker 環境では、開発用のオリジンをチームで統一しておくとトラブルが減る。各自が異なるホスト名やポートを使うと、Service Worker の登録や API の URL が人によって変わる。環境変数で API のベース URL を切り替える場合も、モック時に URL のマッチ条件がずれないようにする。

相対 URL でハンドラーを定義するか、完全な URL を定義するかは、アプリケーションの構成によって決めればよい。フロントエンドと API が同じオリジンに見える構成なら相対 URL が扱いやすい。別ドメインの API を明示的に呼ぶ場合は、実際の URL とモックのマッチ条件を揃える必要がある。ここを曖昧にすると、リクエストは発生しているのにハンドラーだけが反応しない。

未定義のリクエストをどう扱うかも設定しておきたい。すべての未定義リクエストを警告する設定にすれば、意図しない外部通信や、書き忘れたハンドラーを見つけやすい。ただし、ブラウザ拡張機能や解析サービスまで警告対象になる場合があるため、プロジェクトの通信一覧を見ながら運用する。反対に、未定義のリクエストをそのまま通す設定は手軽だが、API の呼び出し先を間違えても気づきにくい。

最初は警告を出す設定から始め、問題が見えた段階でエラーとして扱う方法もある。重要なのは、MSW が起動していることを「画面が表示された」だけで判断しないことだ。ブラウザの開発者ツールで、リクエストがモックされたこと、返されたステータスコードと本文が想定どおりであることを確認する。

結論

MSW は、フロントエンドとバックエンドの「待ち」を減らしながら、ソースコードへモック専用の分岐を大量に追加せずに済む、現実的な選択肢だ。npm install msw --save-devnpx msw init で準備を始め、ハンドラーを追加すれば、実際の API クライアントを使ったまま画面を先行して開発できる。

v2 では rest.get() ではなく http.get()res(ctx.json(...)) ではなく HttpResponse.json() を使う。この差を理解していないと、古い記事のコードを貼り付けたところで動かず、導入そのものが難しく見えてしまう。まずはバージョンに合った API で小さなハンドラーを一つ動かすのがよい。

一方で、「開発環境だけで動かす」「本番ビルドに混入させない」という線引きは、最初に決めておく必要がある。起動処理を一つに集約し、Service Worker の登録状態を確認し、本番ビルドにモックの起動コードや fixture が残っていないかを CI で検査する。便利な仕組みほど、停止条件まで含めて設計しておくべきだ。

REST と GraphQL の両方に対応できる点も MSW の強みだが、モックを本番 API の完全な複製にしようとすると負債化する。画面に必要な正常系、異常系、遅延、空状態を段階的に用意し、バックエンドの契約とずれていないかを確認する。その程度の距離感が、長く使うにはちょうどいい。

最後に、WordPress や Laravel といった既存資産と組み合わせる場合の心得を一つ。

モダンなフレームワークへすべて乗せ替えれば解決する、というのは幻想だ。現実の案件では、古いプラグインと新しい Vue や React が混在し、ACF のフィールド定義と REST API のレスポンスがなかなか噛み合わない。それでも前に進むための道具として、MSW は力技だが頼れる選択肢になる。

技術選択に銀の弾丸はない。必要なのは、現場の制約に合わせて、どこを本物に任せ、どこをモックで先に進めるかを決められる道具だ。MSW は、その判断を実装へ落とし込みやすい。API の完成を待たずに画面を作り、同じ通信処理を使ったまま異常系まで確認できる。フロントエンドの開発を止めないための仕組みとして、導入候補に入れる価値は十分にある。

よくある質問

MSWとjson-serverのような別プロセスのモックサーバーはどう違いますか?
json-serverは別プロセスでAPIを起動しますが、MSWはブラウザ内で動作し、実際のネットワークリクエストを横取りします。MSWはコンポーネントの通信処理を書き換える必要がなく、認証やエラー処理を含めた検証が容易です。
MSW v2への移行で注意すべき点は何ですか?
v1で使用されていたrest.getやres(ctx.json)といったAPIは廃止され、v2ではhttp.getやHttpResponse.jsonを使用します。古い記事のコードをそのまま使うと動作しないため、バージョンに合わせたAPIの記述が必要です。
本番環境でモックが誤って動作しないようにするにはどうすればよいですか?
モックの起動処理を開発環境であることを判定する関数に集約し、本番ビルドのエントリーポイントからはその処理を呼び出さないようにします。また、CIでビルド成果物にモック用のコードが残っていないか検査することも有効です。
MSWがうまく動作しない場合、どこを確認すべきですか?
まずブラウザの開発者ツールでService Workerが正しく登録されているかを確認してください。また、mockServiceWorker.jsがアプリケーションのルートから配信されているか、ページと同じオリジンから取得できているかも重要なチェックポイントです。
GraphQLのモックにも対応していますか?
はい、対応しています。REST APIと同様にハンドラーを定義でき、graphql.queryやgraphql.mutationを使用してオペレーション名や変数に基づいたレスポンスを返すことが可能です。

参考情報