主張
- SPAを作るときは、Node.js/ExpressでAPIモックサーバーを作るといい
- SPAの動作確認・表示確認に有効である
- 特にリアルタイム性があるアプリを数人で動作確認するのに有益
前提
本番用のサーバーはGoやScala、フロントエンドはReactなどで作る想定をしています。
本番用サーバーのコードベースに一つの機能を実装するのは時間多少時間がかかります。なので、新規開発時や機能追加時は先にフロントエンドとモックサーバーを実装することにより企画を固めてから、本番用サーバー開発に手を回すほうが合理的だと考えています。
Swaggerのモックサーバジェネレータで満足できる方は別にそれでいいと思います。
ReactのStorybookで満足できる方も別にそれでいいと思います。
SPAの動作確認・表示確認をするためにステートフルなモックサーバーがほしいことがあります。
例えばTwitterのようなSNSを作っているとして、投稿したらちゃんとタイムラインに表示されるか確認したいことがあります。
そのため、ちゃんとそれっぽい挙動をするモックサーバーを作るといいです。
やること
- ExpressでAPIのモックサーバーを作る
- webpack-dev-serverのproxy機能で1のサーバーに転送するように設定
- フロントエンドは普通に作る
- 基本的にフロントエンドに特殊なコードを書く必要はない
- ただし、本番でFirebaseを使うがモックサーバはWebSocketで済ませたい場合は、そこをすげ替える仕組みがあるといい
- webpackで動かして試してみる
参考までにwebpack.config.jsonを載せておきます。
let webpackHost = "0.0.0.0";
if ("WEBPACK_HOST" in process.env) {
webpackHost = process.env.WEBPACK_HOST;
}
...
devServer: {
contentBase: "./dist",
hot: true,
host: webpackHost,
port: port,
proxy: {
"/api": {
target: `http://${webpackHost}:${port + 1}`,
pathRewrite: {"^/api": ""}
},
"/ws": {
target: `ws://${webpackHost}:${port + 1}`,
ws: true
}
},
},
...
作るモックサーバはどんなものか
データの永続化は不要です。グローバル変数に持っておけばいいです。
立ち上げたらすぐに表示を確認したいので、適当なデータを自動で生成するものを作るといいです。例えばTwitterなら100人のユーザーと100の投稿を自動で作ります。
APIエンドポイントはちゃんとそのAPIを模倣するように作るべきです。
としておくと、フロントエンドを実装したら本当に投稿できるようになっていいです。
サンプルデータをJSONでファイルに保存しておくのも良い手段です。JSONをそのまま返すのではなくて、スピンアップ時にJSONを読んでグローバル変数に入れるようにするとフロントエンドの操作に合わせて動的な反応ができていいです。
サンプルコード
// 必要に応じてサンプルデータも入れておくといいです
interface Data {
tweets: Tweet[];
users: User[];
}
let data: Data = {
users: [],
tweets: [],
};
router.get("/api/tweets", (req, res) => {
res.json(data.tweets);
});
router.post("/api/tweets", (req, res) => {
let tweet = req.body as Tweet;
let tweetID = randomBytes(16).toString('hex');
tweet.tweetID = tweetID;
res.status(201);
res.send(tweetID);
});
開発コストをどう考えるか
永続化をせず全てグローバル変数を使うので大変ではないと思います。
セキュリティのことも考えなくていいので簡単だと思います。
大体 RESTFul APIといえば
- get
- post
- put
- delete
です。なので一つのエンドポイントをJSの
- find
- concat
- filter
を使って書けば、後は他のエンドポイントにコピペするだけです。
Swaggerを使っている人には関係ない話ですが、僕は使わないことにしたので、フロントエンドとJSONの型定義用TypeScriptファイルを使いまわせるので楽です。
なので、大してコストはかからないと思います
ファイル構成についてのアドバイス
今の所これが最善だと思っています。
リポジトリを分けることにした理由は、求められるtsconfigが違って辛いからです。
そんなことはtsconfigを2つ用意してコンパイル時に引数で分ければいいではないかと思うかもしれないですが、僕が使っているWebStormがうまくいかなくなるのでやむを得ないです。
他にも、WebStormのSuggestionが汚れて辛いとかの理由があります。
5は大変ダーティーですが、所詮モックサーバーなのでまあいいとします。
本当はフロントエンドとモックサーバーは双方向に依存していますが、package.json上で双方向に依存しているとライブラリ関係でろくなことにならないのでこうします。
- フロントエンドとモックサーバーはリポジトリを分ける
- フロントエンドがモックサーバーをインポートする
- フロントエンドでnpm run startするとwebpackとモックサーバーが両方立ち上がるようにしておく
- 開発PCではフロントエンドとモックサーバーを隣のディレクトリに並べる
- モックサーバがフロントエンドリポジトリ内のファイル(主にTypeScriptによるJSONの型定義ファイル)が必要な場合は ". ./隣のリポジトリ/some/file.ts"とインポートする。
本番サーバーができたら
本番サーバーが出来上がってもメンテするべきです。
なぜなら、企画→フロントエンドだけ作ってみる→実機で見て企画を練る→ というサイクルを回すために、このモックサーバーは使われ続けるからです。
GoやScala製の本番サーバは永続化層のコードを書く必要があるため、モックサーバに比べて開発のオーバーヘッドが高いはずです。常にどんな企画もモックサーバーで手早く実機確認できるようにしてあるといいです。
また、本番が始まったらassetsやマスタデータを本番から落としてくるシェルスクリプトを作っておくと、いつでもそれらしいデータを表示できるのでおすすめです。
副次的な効能
リアルタイム性があるような動き(例えば、Tweetが投稿されたら他人のタイムラインに投稿が表示されるなど)が早期に仮実装されると、企画さんが喜びます。その後が遅れちゃっても少々大目に見てくれます。フィードバックは大事です。