😤

まだAPIが出来ていない?私は一向にかまわんッッ

2021/11/16に公開

初記事です。

あるフロントエンドの悩み

プロジェクトの納期は決まっていて、すぐに着手しないといけない。
だけど、API はまだできていない...
早く着手したい...、ふぬぬ...。

今回はそんなフロントエンドがバックエンドのタスクの待ちにならないように、並行してアジャイルに開発していけるツールとしてmswをご紹介します。

mswとは?

mswとはmock service workerの略で、簡単にモックのAPIを立てることができるライブラリです。
同様のことができるライブラリとしてJSON Serverもありますが、mswには以下の利点があります。

  • 単純にJSONを返すのではなく、ロジックも書くことができる
  • Expressのようにスッキリ書くことができる
  • RESTだけでなく、GraphQLにも対応している

以下のようなケースに向いていると思います。

  • 早く着手しないといけないけど、API はまだできていない
  • 要件がフワッとしているので、動くデモを作って仕様を固めたい
  • テストに使うモックのデータが欲しい

mswの使い方

まずmswのパッケージを追加し、セットアップします。

$yarn add -D msw
$npx msw init public/ --save

次に、ベースとなるファイルを配置します。

worker.ts
const handlers = [
  rest.get('/', (req, res, ctx) => res(
    ctx.status(200),
    ctx.json({message: "hello, world!"})
  ))
]export const worker = setupWorker(...handlers);
index.ts
import { worker } from '../mock/worker';

if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_USE_MOCK_SERVER === 'true') {
  void worker.start();
}

基本はこれだけです。
あとはお好きなHTTPクライアントでgetを投げれば、{message: "hello, world!"}が返ってきます。

これだけだとJSONファイル置いとけばええやんってなるかもしれないので、ロジックを少し足していきたいと思います。

例: ユーザー単体取得

例として、ユーザーの単体取得の MockAPI を実装します。

mockData/users.ts
export const mockUsers = [
  {
    id: 1,
    name: '日本 太郎',
  },
  {
    id: 2,
    name: 'Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima Trinidad Ruiz y Picasso',
  },
];
mockApi/getUser.ts
import {
  ResponseResolver,
  RestContext,
  RestRequest,
  MockedResponse,
  DefaultRequestBody,
} from 'msw';
import { mockUsers } from './mockData/users';

export const getUser: ResponseResolver<
  RestRequest<DefaultRequestBody, { userId: string }>,
  RestContext,
> = (req, res, ctx) => {
  const user = mockUsers.find((mockUser) => mockUser.id === parseInt(req.params.userId, 10));
  return res(
    ctx.json({
      user,
    }),
  );
};
worker.ts
import { getUser } from './mockApi/getUser';

export const handlers = [rest.get('/:userId', getUser)];

楽々ですね。
これで、例えばaxiosなら

  const fetchUserWithAxios = async () => {
    const res: AxiosResponse<{id: number, name: string}> = await axios.get(`https://localhost:3000/${userId}`)

    return res.data;
  };

みたいに呼んであげたらOKです。

実際のAPIを叩いているかのように実装できるので、APIが仕上がったらエンドポイントを変えるだけで済みます。
下記のように環境変数を設定して、モックと実際のAPIを簡単に切り替えられるようにしておくと良いかもしれません。

.env
REACT_APP_USE_MOCK_SERVER = 'false'
REACT_APP_MOCK_API_ENDPOINT= 'https://localhost:3000'
REACT_APP_API_ENDPOINT = 'http://actual-api-endpoint'
export const endpoint =
  process.env.REACT_APP_USE_MOCK_SERVER === 'true'
    ? process.env.REACT_APP_MOCK_API_ENDPOINT
    : process.env.REACT_APP_API_ENDPOINT;

まとめ

いかがでしたでしょうか。
mswはサクッとモックAPIが作れて、アジャイルな開発には特に向いているライブラリです。
今回は省きましたが、Storybookと組み合わせるとさらに便利です。
デザインカンプの完成すら待つ必要がなくなり、フロントエンドがタスクの依存関係から解放されるかもしれません。
それについては別記事にしたいと思います。

最後までお付き合いいただき、ありがとうございました。

参考

mswをテストに使う利点と方法については以下も良記事なのでぜひ
Stop mocking fetchxw

(追記 2021/11/29) workerのimportについて

運用していくうちに気づいた点があったので、訂正させていただきます。
下記の部分についてですが、

index.ts
import { worker } from '../mock/worker';

if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_USE_MOCK_SERVER === 'true') {
  void worker.start();
}

こうすると本番ビルドしたファイルの中にmswのコードが含まれてしまうので、

index.ts
if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_USE_MOCK_SERVER === 'true') {
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, global-require, @typescript-eslint/no-var-requires
  const worker = require('./tests/handlers/worker');
  void (worker as { default: { start: () => void } }).default.start();
}

というようにして、if文の中でworkerをimportしてきた方が良さそうです。
失礼致しました🙇‍♂️

GitHubで編集を提案

Discussion