Skip to main content

第20章:HTTPレスポンス設計(200/201/202/409など)📨🔁

この章のゴール🎯✨

  • 「このAPI、次に何すればいいの…?😵」をクライアントに感じさせないレスポンスを作れるようになる💪
  • 冪等性(Idempotency)と相性のいい ステータスコード + ヘッダー + エラー形式 をセットで決められるようになる🔑📦
  • ミニ注文API(注文作成+支払い確定っぽい)で、レスポンス一覧表を完成させる📑✅

Concept


1) まず大前提:ステータスコードは「会話の合図」だよ💬🚦

APIは、レスポンスでクライアントにこう伝えてるのと同じ👇

  • 200 / 201 → 「OK!結果はこれだよ😊」
  • 202 → 「受け取った!でもまだ処理中だよ⏳(あとで確認してね)」
  • 409 → 「今の状態とぶつかってるよ⚔️(競合!)」
  • 429 → 「今は混みすぎ!ちょっと待って🙏」
  • 503 → 「一時的に無理!時間をおいてね🛠️」

この“合図”がブレると、クライアントは 「再送していい?やめた方がいい?同じ結果返る?😇」って迷って事故るの…💥

HTTPの基本の意味(201/202/409/503など)はHTTP仕様(RFC 9110)に基づくよ📘✨ (rfc-editor.org)


2) 冪等性とレスポンス設計がぶつかるポイント⚡

冪等性が必要になるのは、だいたいこの状況👇

  • クライアントが送った
  • サーバーは実行した(かもしれない)
  • でもクライアントは 返事を受け取れなかった(タイムアウト/通信切断)📡💔
  • だからクライアントが 同じリクエストを再送 🔁

このとき、サーバーがちゃんと設計されてると…

✅ 同じ Idempotency-Key なら、同じ結果(同じレスポンス)を返せる🎁 IETFでも Idempotency-Key ヘッダーをPOST/PATCHで“フォールトトレラントにする”ための仕様が策定中だよ🧪 (IETF Datatracker) 実務例としてStripeも Idempotency-Key を使い、同じキーは結果をキャッシュして返す運用をしてるよ📦 (Stripe Docs)


3) まず覚える「勝ちパターン」ステータスセット🏆✨

よく使うのはこのへん(ミニ注文APIで使うのもここ!)👇

成功系😊

  • 200 OK:普通に成功。結果も返す📦
  • 201 Created:新しいリソース作った!場所は Location で教えるのが定番📍 (rfc-editor.org)
  • 204 No Content:成功だけど返すもの無し(DELETE成功とか)🧼

処理中⏳

  • 202 Accepted:受け付けたけど、まだ終わってない(非同期/時間かかる)🌀 (rfc-editor.org)

失敗系(クライアント原因)😵

  • 400 Bad Request:形式がダメ(JSON壊れてる等)🧱
  • 401 Unauthorized / 403 Forbidden:認証/権限🔐
  • 404 Not Found:ない🙈
  • 409 Conflict:状態がぶつかった⚔️(冪等キー衝突・二重確定など) (rfc-editor.org)
  • 422 Unprocessable Content/Entity:形式は合ってるけど、内容が処理できない(バリデーションなどで使われがち)🧾 ※もともとはWebDAVで定義されたコードだよ📘 (rfc-editor.org)
  • 429 Too Many Requests:レート制限。Retry-After で待ち時間を伝えられる⏲️ (IETF Datatracker)

失敗系(サーバー原因)🔥

  • 500:内部エラー💥
  • 503 Service Unavailable:一時的に無理。Retry-After を付けられる🛠️⏳ (rfc-editor.org)

4) 「エラー本文」は統一フォーマットにしよう📦🧩

ステータスコードだけだと情報が足りないことが多いよね😇 そこで Problem Details 形式が便利!

  • application/problem+json の形で、機械にも人にもわかるエラーを返せる📘✨
  • RFC 9457 が現在の仕様(RFC 7807を置き換え)だよ🔁 (rfc-editor.org)

Problem Details の例(イメージ)🧾

{
"type": "https://example.com/problems/idempotency-key-conflict",
"title": "Idempotency key conflict",
"status": 409,
"detail": "Same Idempotency-Key was used with a different request body.",
"instance": "/orders"
}

💡ポイント

  • type:エラー種類(URLっぽい識別子)🔖
  • title:短い見出し🪧
  • status:HTTPステータス(本文にも入れると親切)🧠
  • detail:人間向け説明📣
  • instance:起きた場所(リクエストパスなど)📍

5) ミニ注文API:レスポンス設計の「完成形」サンプル📑✨

ここでは例として👇の2つを考えるね!

  • POST /orders:注文を作る🧾
  • POST /orders/{orderId}/pay:支払い確定っぽい処理💳

A. POST /orders のレスポンス候補🧾📨

状況ステータス返すもの補足
注文作成成功201注文JSON + Location: /orders/{id}“作った”ので201が自然📍 (rfc-editor.org)
同じIdempotency-Keyの再送(すでに成功済み)最初と同じ(例:201)最初と同じ本文冪等の美しさ✨(同じキー=同じ結果)
同じキーだが本文が違う(危険!)409(または422)Problem Details「キー使い回し事故」⚔️
バリデーションNG(例:金額がマイナス)422(または400)Problem Details422は“内容が処理できない”の意味でよく使われる🧾 (rfc-editor.org)
レート制限429Problem Details + Retry-After待ち時間を伝えよう⏲️ (IETF Datatracker)
一時的に過負荷/メンテ503Problem Details + Retry-After一時的なら503が自然🛠️ (rfc-editor.org)

B. 「処理中」どう返す?:202の使いどころ⏳🌀

たとえば支払い処理って、外部決済っぽくて時間かかる想定にしたいよね💳🌧️ そのとき便利なのが 202 Accepted

  • 202:「受け付けたよ。完了はまだ!」
  • 追加で 状態確認URLLocation などで教えると親切📍

Retry-After は、503のほか、待ってほしいときに使われるヘッダーとして定義されてるよ⏲️ (rfc-editor.org)

✅ 例:支払い処理が開始されたら…

  • 202 Accepted
  • Location: /operations/{operationId}(確認先)
  • 本文:{ "operationId": "...", "status": "processing" }

6) 「409 Conflict」ってどんなとき?冪等性だと超重要⚔️🔑

409は「今の状態と矛盾して処理できない」って意味で使うよ📘 (rfc-editor.org)

冪等性でありがちな409例👇

例1:同じIdempotency-Keyなのに、本文が違う😱

  • 1回目:amount=1000
  • 2回目:amount=2000(同じキー)

これは “別の操作を同じキーでやろうとした” ってことだから超危険💥 → 409(or 422)で落とすのが安全⚔️

例2:状態遷移の競合(もう支払い済みなのに、再度pay)💳💥

  • paid なのに pay が来た → 409(状態がぶつかってる)

7) TypeScript実装ミニ例:ステータスとProblem Detailsを返す🧑‍💻✨

Problem Details を返すヘルパー🧩

import type { Response } from "express";

type ProblemDetails = {
type: string;
title: string;
status: number;
detail?: string;
instance?: string;
// 拡張フィールドもOK(例: code, errors など)
[key: string]: unknown;
};

export function sendProblem(res: Response, problem: ProblemDetails) {
return res
.status(problem.status)
.type("application/problem+json")
.json(problem);
}

409(冪等キー衝突)を返す例⚔️

import type { Request, Response } from "express";
import { sendProblem } from "./sendProblem";

export function createOrder(req: Request, res: Response) {
const key = req.header("Idempotency-Key");
if (!key) {
return sendProblem(res, {
type: "https://example.com/problems/idempotency-key-required",
title: "Idempotency-Key is required",
status: 400,
detail: "Please send Idempotency-Key header for POST /orders."
});
}

// ここでは例として「同じキーが別payloadで来た」扱いにする
const conflict = false; // ←本当は保存済みリクエストと比較する
if (conflict) {
return sendProblem(res, {
type: "https://example.com/problems/idempotency-key-conflict",
title: "Idempotency key conflict",
status: 409,
detail: "Same Idempotency-Key was used with a different request body.",
instance: "/orders"
});
}

// 成功(201)
const orderId = "ord_123";
return res
.status(201)
.location(`/orders/${orderId}`)
.json({ orderId, status: "created" });
}

8) 演習📝✨(ミニ注文APIのレスポンス表を作ろう!)

演習1:レスポンス一覧を完成させよう📑✅

次を埋めてみてね👇(あなたの正解が“APIの契約”になるよ!)

  • POST /orders

    • 成功:___(200/201/202どれ?)
    • 冪等リトライで成功済み:___(同じ?変える?)
    • 同じキー本文違い:___(409/422/400…どれ?)
    • バリデーションNG:___(422/400…どっち?)
  • POST /orders/{id}/pay

    • 処理がすぐ終わる成功:___
    • 時間がかかる(外部決済想定):___(202?)
    • すでに支払い済み:___(409?)

🎀コツ:クライアントが「次に何するか」を迷わない答えを選ぶのが勝ち!


演習2:Retry-Afterを付けるのはどれ?⏲️

次のうち「Retry-Afterが特に相性いい」のを選ぼう👇

  • A) 401
  • B) 429
  • C) 503

(ヒント:429はRFC 6585でRetry-Afterを付けられるって書かれてるよ📘 (IETF Datatracker))


9) AI活用プロンプト🤖💡(コピペOK)

① ステータス表のたたき台を作らせる📋

ミニ注文APIのレスポンス設計をしたいです。
エンドポイントは POST /orders と POST /orders/{id}/pay です。
冪等性のため Idempotency-Key を使います。
成功/処理中/競合/バリデーション/レート制限/過負荷 のケースごとに
推奨ステータスコードと、返すJSONの例(Problem Details形式も)を表で出してください。

② あなたの案を“ツッコミ役”にレビューさせる🔍

以下が私のレスポンス設計案です。
「クライアントが迷う点」「冪等性と相性が悪い点」「ステータスがブレている点」を指摘して、
改善案を出してください。

(ここにあなたのレスポンス表を貼る)

10) まとめ🌸(この章の必勝ルール)

  • 同じIdempotency-Keyなら、原則“同じレスポンス”を返すのが一番わかりやすい🔁✨(実務でも採用例あり)(Stripe Docs)
  • 201は「作った」合図Location を付けると親切📍 (rfc-editor.org)
  • 202は「受け付けた、まだ」。確認先(状態URL)を用意すると迷わせない⏳🌀 (rfc-editor.org)
  • 409は「状態がぶつかった」(冪等キー衝突、二重確定など)⚔️ (rfc-editor.org)
  • 429/503はRetry-Afterで“待ち時間”を言える⏲️ (IETF Datatracker)
  • エラー本文は Problem Details(RFC 9457) に寄せると、整って強い📦✨ (rfc-editor.org)