http.createServer() は、リクエストハンドラーという1つの関数からWebサーバーを作ります。Node.jsはリクエストが来るたびに、この関数を2つのオブジェクトとともに呼びます。req(クライアントが送ったメソッド、URL、ヘッダー)と res(返すもの)です。res にステータスコードとヘッダーを設定し、ボディを渡して res.end() を呼びます。server.listen(3000) でポート3000の接続を受け付け始めます。
最初のリクエストには You asked for GET /hello?name=Ada と返事が返り、Consoleにも同じメソッドとURLが記録されます。リクエストバーから DELETE /anything を送ってみてください。同じハンドラーが答えます。素の http サーバーにはルートがなく、すべてのリクエストが1つの関数を通るからです。
JSONを送り、ステータスコードを設定する
res.writeHead(status, headers) は、ステータスコードとヘッダーを1回の呼び出しで設定します。JSONなら Content-Type: application/json を設定し、JSON.stringify() の結果を送ります。res.end() が受け付けるのは文字列かBufferだけです。
GET /users?limit=1 は [{"id":1,"name":"Ada"}] を返します。req.url はパスとクエリ文字列を合わせたものなので、new URL(req.url, 'http://localhost') で pathname と searchParams に分けます(ベースURLは、URL が絶対アドレスを必要とするから付けているだけです)。GET /nothing を送ると、ステータスは404になります。
switchでリクエストをルーティングする
小さなAPIなら、メソッドとパスに対する switch で十分です。'GET /users' のようなキーを作り、ルートごとに case を用意します。
ボディを {"name": "Grace"} にして POST /users を送ると、201 と {"id":2,"name":"Grace"} が返り、GET /users は2人のユーザーを返すようになります。同じPOSTをボディ hello で送ると、400 と {"error":"Body must be JSON"} が返ります。
POSTボディを読む
Node.jsはボディをパースしてくれません。req は読み取り可能なストリームで、ボディは data イベントでかたまりごとに届き、すべて届くと end が発生します。上の readBody() はかたまりを文字列に集めてPromiseを返すので、ハンドラーはそれを await できます。req は非同期イテラブルなので、次のループでも同じことができます。
let data = '';
for await (const chunk of req) data += chunk;
const body = JSON.parse(data);
実際のサーバーではサイズも制限し(data.length が上限を超えたら読むのをやめて 413 を返す)、JSON.parse を呼ぶ前に req.headers['content-type'] を確認します。
よくある間違い
res.end() を忘れる。 ステータスを設定してもレスポンスを終えない分岐があると、クライアントはタイムアウトするまで待たされます。
ボディの後でヘッダーを設定する。 res.end() や最初の res.write() が実行された時点でヘッダーは送信済みなので、res.setHeader() は ERR_HTTP_HEADERS_SENT を投げます。例のように、早めにレスポンスを送るときは return を使いましょう。
req.url をパスと比較する。 req.url はクエリ文字列を含むので、/users?limit=1 に対して req.url === '/users' はfalseです。pathname を比較しましょう。
Expressを使うべきとき
ヘルスチェック、Webhookの受け口、HTTPの仕組みを学ぶ用途なら http モジュールで十分です。/users/:id のようなルートパラメーター、JSONボディのパース、ミドルウェア、エラー処理が必要になると、結局は自分で小さなフレームワークを書くことになります。Expressがまさにそのフレームワークで、同じ req と res オブジェクトの上に作られています。Expressのルーティングで、同じAPIを app.get() と app.post() で書いた例を紹介しています。
よくある質問
res.writeとres.endの違いは何ですか?
res.write() はボディの一部を送り、レスポンスを開いたままにするので、何回でも呼べます。res.end() は省略可能な最後のかたまりを送ってレスポンスを終えます。どのレスポンスにも res.end() がちょうど1回必要です。
NodeのHTTPサーバーを止めるには?
server.close() を呼びます。新しい接続の受け付けをすぐにやめ、開いている接続が終わったらコールバックを実行します。SIGTERM を受けてきれいに終了するには、これが必要です。
httpモジュールでHTTPSを使うには?
代わりに https モジュールを使います。https.createServer({ key, cert }, handler) は同じハンドラーに加えて秘密鍵と証明書を受け取ります。本番ではたいていNginxのようなプロキシやホスティング環境がTLSを終端し、Nodeのサーバーは普通のHTTPで待ち受けたままです。
Nodeのサーバーはどのポートで待ち受けるべきですか?
1024より大きい空きポートなら、管理者権限なしで使えます。開発では3000や8080がよく使われます。ホスティング環境はたいてい環境変数でポートを教えてくれるので、server.listen(process.env.PORT || 3000) と書きましょう。