今回やること
この記事では、フォーム送信とJSON APIのHTTP上の違いを確認します。
同じPOSTでも、HTMLフォーム、JSON API、ファイルアップロードでは、送られるContent-Typeとボディの形が違います。
「POSTしているのにサーバーで値が取れない」という不具合は、この違いを見落としていると起きやすいです。
3つの送信パターン
Webアプリでよく見る送信パターンは次の3つです。
| パターン | 主なContent-Type | よく使う場面 |
|---|---|---|
| 通常のフォーム | application/x-www-form-urlencoded | 問い合わせ、検索、ログイン |
| JSON API | application/json | SPA、モバイルアプリ、外部API |
| ファイルアップロード | multipart/form-data | 画像、PDF、添付ファイル |
どれもHTTPリクエストですが、サーバー側の受け取り方が変わります。
通常フォームのGET
検索フォームでは、GET がよく使われます。
<form action="/search" method="get">
<input name="q" value="javascript">
<button>検索</button>
</form>
送信後のURLは次のようになります。
/search?q=javascript
HTTPとして見ると、本文ではなくクエリに値が入ります。
GET /search?q=javascript HTTP/1.1
Host: example.com
GETフォームは、検索条件や絞り込み条件のように、URLで共有しても問題ない情報に向いています。パスワード、住所、電話番号、問い合わせ本文のような情報をGETで送るのは避けます。
通常フォームのPOST
問い合わせやログインでは、POST がよく使われます。
<form action="/contact" method="post">
<input name="name" value="Ada">
<textarea name="message">hello</textarea>
<button>送信</button>
</form>
この場合、HTTPリクエストは次のような形になります。
POST /contact HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
name=Ada&message=hello
application/x-www-form-urlencoded は、フォームの値を key=value&key=value の形で送る形式です。
サーバー側では、HTMLフォームを処理する仕組みで本文を読み取ります。JSONとして読む処理だけを書いていると、この形式の値を受け取れないことがあります。
JSON APIのPOST
JavaScriptの fetch でAPIに送る場合は、JSONがよく使われます。
await fetch("/api/contact", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
name: "Ada",
message: "hello"
})
});
HTTPとして見ると、次のようになります。
POST /api/contact HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{"name":"Ada","message":"hello"}
JSON APIでは、Content-Type: application/json とJSON文字列の本文がセットです。
よくあるミス:
| ミス | 起きること |
|---|---|
JSON.stringify しない | bodyが想定通りの文字列にならない |
Content-Type を付け忘れる | サーバーがJSONとして読まない |
| キー名が違う | バリデーションエラーになる |
Accept を指定しない | HTMLエラーが返ることがある |
POST しているかどうかだけでなく、本文がどの形式で送られているかを確認します。
ファイルアップロード
画像やPDFを送る場合は、multipart/form-data が使われます。
<form action="/profile/avatar" method="post" enctype="multipart/form-data">
<input type="file" name="avatar">
<button>アップロード</button>
</form>
HTTPとしては、複数の部品に分かれた本文になります。
POST /profile/avatar HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----abc
------abc
Content-Disposition: form-data; name="avatar"; filename="profile.png"
Content-Type: image/png
...binary...
------abc--
multipart/form-data では、ブラウザが boundary を付けて本文を区切ります。
JavaScriptで FormData を使う場合、通常は Content-Type を手動で設定しません。手動で multipart/form-data だけを書いてしまうと、必要な boundary が欠けてサーバーが読めないことがあります。
const formData = new FormData();
formData.append("avatar", file);
await fetch("/profile/avatar", {
method: "POST",
body: formData
});
JSON APIとフォームの使い分け
どちらが正しいというより、用途で選びます。
| 用途 | 向いている形式 |
|---|---|
| 通常のWebページ内フォーム | HTMLフォーム |
| JavaScript中心の画面 | JSON API |
| 外部システムと連携 | JSON API |
| ファイルアップロード | multipart/form-data |
| 検索や絞り込み | GET + クエリ |
初学者が混乱しやすいのは、画面では同じ「送信ボタン」に見えても、HTTP上ではかなり違う形になる点です。
DevToolsで確認する場所
Chrome DevToolsのNetworkパネルで対象リクエストを選びます。
| 見たいもの | 場所 |
|---|---|
| メソッド | Headers > General |
| URL | Headers > General |
Content-Type | Headers > Request Headers |
| クエリ | Payload または Headers |
| フォーム本文 | Payload |
| JSON本文 | Payload |
| ファイル送信 | Payload |
送信できていないように見えるときは、まずNetworkにリクエストが出ているかを確認します。リクエスト自体が出ていなければ、JavaScriptエラー、フォームのイベント処理、ボタンの種類などを見ます。
curlで比べる
フォーム形式で送る例:
curl -X POST https://example.com/contact \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "name=Ada&message=hello"
JSONで送る例:
curl -X POST https://example.com/api/contact \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data '{"name":"Ada","message":"hello"}'
ファイルを送る例:
curl -X POST https://example.com/profile/avatar \
-F "avatar=@profile.png"
curl でも、フォーム形式、JSON、ファイルアップロードで指定の仕方が変わります。ブラウザで送っている内容と同じ形を再現できると、フロント側の問題かサーバー側の問題かを切り分けやすくなります。
よくある不具合
| 症状 | よくある原因 |
|---|---|
| サーバーで値が空になる | Content-Type と本文形式が合っていない |
| JSON parse error | JSONとして不正な文字列を送っている |
415 Unsupported Media Type | サーバーがその形式を受け付けていない |
| ファイルが空になる | multipart/form-data の扱いが間違っている |
| ログイン後に戻される | Cookieが送られていない |
| CORSで止まる | ブラウザのオリジン制限に引っかかっている |
400 や 415 が返ったときは、サーバーが悪いと決めつけず、リクエストの形式を先に確認します。
まとめ
HTMLフォーム、JSON API、ファイルアップロードは、同じHTTP通信でもリクエストの形が違います。
確認するポイントは、メソッド、URL、Content-Type、ボディの形式です。特に POST の不具合では、「POSTしているか」よりも「何形式でPOSTしているか」を見ることが重要です。