fetch で HTTP POST(JSON)の基本と実践
fetch は HTTP リクエストを送る API。JSON を POST する場合は、メソッド指定・本文の JSON 化・ヘッダー指定(Content-Type)を組み合わせます。レスポンスの確認とエラーハンドリングまで含めるのが定石です。
基本の書き方
const url = "https://api.example.com/items";
const data = { title: "JavaScript入門", price: 1200 };
const r = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json", // 本文がJSONであることを宣言
"Accept": "application/json", // 返却もJSONを期待(任意)
},
body: JSON.stringify(data), // オブジェクト→JSON文字列に変換
});
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const json = await r.json();
console.log(json);
JavaScript- ポイント: 本文は必ず
JSON.stringify(...)で文字列化。Content-Type: application/jsonを付けるとサーバー側で JSON として扱われます。
すぐ使えるテンプレート集
テンプレート1: 安全な POST(HTTPチェック+JSON受け取り)
async function postJSON(url, payload) {
const r = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json", "Accept": "application/json" },
body: JSON.stringify(payload),
});
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
}
// 使用例
const result = await postJSON("/api/books", { title: "JS", author: "Aki" });
console.log(result);
JavaScriptテンプレート2: タイムアウト付き(AbortController)
async function postWithTimeout(url, payload, ms = 3000) {
const ac = new AbortController();
const timer = setTimeout(() => ac.abort(), ms);
try {
const r = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
signal: ac.signal,
});
clearTimeout(timer);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return await r.json();
} catch (e) {
if (e.name === "AbortError") throw new Error("Timeout");
throw e;
}
}
JavaScript- ポイント: fetch はデフォルト無期限。締め切りは AbortController を使います。
テンプレート3: 認証トークン付き
async function postAuth(url, payload, token) {
const r = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
"Accept": "application/json",
},
body: JSON.stringify(payload),
});
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
}
JavaScriptテンプレート4: 成功・失敗を allSettled で集約
const payloads = [{ a: 1 }, { a: 2 }, { a: 3 }];
const settled = await Promise.allSettled(
payloads.map(p => fetch("/api/bulk", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(p),
}).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`))))
);
// 成功だけ抽出
const ok = settled.filter(r => r.status === "fulfilled").map(r => r.value);
const ng = settled.filter(r => r.status === "rejected").map(r => r.reason);
JavaScript実務でのポイント
- ヘッダーと本文の整合性: JSON を送るなら本文は
JSON.stringify、ヘッダーはContent-Type: application/jsonをセット。受け取りも JSON ならAccept: application/jsonを付けると意図が明確。 - HTTP エラーチェック:
response.ok(2xx)を確認し、失敗は例外化して共通処理へ流すのがシンプル。 - CORS とサーバ設定: クロスオリジンでの POST はサーバー側の CORS 設定が必須(プリフライトも発生しうる)。ブラウザ側だけでは回避不能。
- JSON 以外の送信: 画像やファイル、フォーム送信は
FormDataやBlob/ArrayBufferを選択。JSON と使い分ける。
よくある落とし穴と対策
- オブジェクトをそのまま body に入れる: 文字列化されず送信形式が不正。
- 対策: 必ず
JSON.stringify(data)。
- 対策: 必ず
- Content-Type を付けない: サーバーが JSON と認識できず 400/415 になる。
- 対策:
headers: { "Content-Type": "application/json" }。
- 対策:
- HTTP 失敗を見落とす: 2xx 以外は
ok=false。- 対策:
if (!r.ok) throw new Error(...)で明示処理。
- 対策:
- タイムアウト未設定: フリーズの原因。
- 対策: AbortController で締め切り。
- 直接 JSON 化できない型:
Date/Map等はそのままでは想定外の文字列に。- 対策: 送信前に整形(文字列化・オブジェクト化)。
練習問題(手を動かして覚える)
// 1) 基本POST(HTTPチェック)
async function ex1() {
const r = await fetch("/api/echo", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ msg: "hello" }),
});
if (!r.ok) throw new Error(`HTTP ${r.status}`);
console.log(await r.json());
}
// 2) 認証トークン付きPOST
async function ex2() {
const token = "demo-token";
const r = await fetch("/api/me", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
"Accept": "application/json",
},
body: JSON.stringify({ action: "whoami" }),
});
if (!r.ok) throw new Error(`HTTP ${r.status}`);
console.log(await r.json());
}
// 3) タイムアウト(2秒)付きPOST
async function ex3() {
const ac = new AbortController();
const t = setTimeout(() => ac.abort(), 2000);
try {
const r = await fetch("/api/slow", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ n: 1 }),
signal: ac.signal,
});
clearTimeout(t);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
console.log(await r.json());
} catch (e) {
console.log(e.name === "AbortError" ? "timeout" : e.message);
}
}
// 4) まとめてPOSTして部分成功を扱う
async function ex4() {
const payloads = [{ id: 1 }, { id: -1 }, { id: 2 }];
const settled = await Promise.allSettled(payloads.map(p =>
fetch("/api/items", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(p),
}).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))
));
const ok = settled.filter(r => r.status === "fulfilled").map(r => r.value);
const ng = settled.filter(r => r.status === "rejected").map(r => r.reason.message);
console.log({ ok, ng });
}
JavaScript