Nodeの標準ライブラリでAPIを立てる
目標
Nestを使わず、Nodeの標準ライブラリだけで小さな注文APIを作ります。ルーティング表、 入力検証、出力の形、依存性の注入、イベントループまでの8つのステップです。
なぜこうするのか
ラボのPodはDNSしか開いていないので、npm installができません。ところがその制約が
かえってこのコースに合っています。フレームワークが代わりにしてくれていた判断を自分で
下してみると、あとでNestを読むときに、各仕組みが何を代わりにしているのかが見えてきます。
形式
/root/work/api/app.mjsを作り、次をexportします。
handle(req):reqは{method, path, body, headers}で、返す値は{status, body}です。本物のソケットは開きません。createApp({store}): ステップ6で作ります。
ソケットを開かないのは、採点がポートの衝突や起動タイミングに左右されてはいけないからです。 失敗が本当の失敗を意味してこそ、テストが役に立ちます。
サーバーの骨組みを作る
mkdir -p /root/work/apiを実行してから、app.mjsにexport async function handle(req)を置いてください。reqは{method, path, body, headers}で、返す値は{status, body}です。/healthzは200と{status:'ok'}を返します。
handle('/healthz')が200と{status:'ok'}を返しています。
ルーティング表を作る: 404と405を分ける
パスの一覧を配列で持ち、まずパスが合うものを集めてからメソッドを見てください。パスがなければ404、パスはあるのにメソッドがなければ405です。GET /itemsは配列を返します。
存在しないパスは404、存在するパスに対する別のメソッドは405です。
入力を検証し、なぜ誤りなのかを知らせる
POST /itemsは{name: string, qty: number}を受け取ります。誤っていれば400と{errors:[...]}で、どのフィールドがなぜ誤っているのかを入れてください。正常なら201です。
qtyが文字列なら400でerrorsにqtyが含まれ、正常なら201です。
保存した形と外へ出す形を分ける
ストアにはsecretがありますが、レスポンスにはあってはいけません。ハンドラーごとに消すのではなく、出力するフィールドを1か所で選んでください。許可リストです。
GET /itemsのレスポンスにsecretがなく、id・name・qtyはあります。
存在しないものには404を返す
GET /items/:idを作ります。存在しなければ404です。200に{error:...}を入れると、クライアントは成功と読んでしまいます。ステータスコードが契約です。
存在しないidは404、存在するidは200とその項目です。
ストアを注入で受け取る
export function createApp({store})を作り、ハンドラーがそのstoreだけを使うようにしてください。グローバルを直接参照すると、次のステップで差し替えられません。
createApp({store})に渡したストアが実際に使われ、出力の形も保たれています。
イベントループを塞がない
GET /slowを作り、最低でも40msかかるようにしてください。whileで時間を消費すると、その間はタイマーまで止まります。await new Promise(r => setTimeout(r, 50))で処理を譲ってください。
4つのリクエストを同時に処理している間も、5msのタイマーが動き続けています。
サーバーを起動せずに契約をテストする
app.test.mjsに、node --testで動くテストを2件以上書いてください。createAppで偽のストアを注入してテストします。ポートを開いてはいけません。
node --testが2件以上成功し、サーバーを起動していません。