Content-Typeは、HTTPリクエストやレスポンスの本文がどの形式で送られているかを相手に伝えるヘッダーです。JSONを送る場合は application/json、通常のフォーム送信では application/x-www-form-urlencoded、ファイルアップロードでは multipart/form-data などを使います。本文の形式とContent-Typeがずれると、サーバー側がリクエストボディを正しく解析できず、「JSONが空になる」「値が取れない」「ファイルが届かない」といった原因になります。

1. Content-Typeとは何か
1-1. 本文の形式を相手に伝えるヘッダー
Content-Typeは、HTTPメッセージの本文がどの形式で書かれているかを相手に伝えるヘッダーです。API連携では、送るデータの中身とContent-Typeを一致させることが重要です。
HTTPリクエストやレスポンスには、ヘッダーと本文があります。本文にはJSON、フォームデータ、HTML、画像、ファイルなどさまざまな形式のデータが入ります。受け取る側は、Content-Typeを見て「これはJSONとして読む」「これはフォームとして読む」と判断します。つまり、Content-Typeは本文の読み方を伝えるラベルのような役割です。
たとえば、本文がJSONなのにContent-Typeが付いていない、またはフォーム用のContent-Typeになっていると、サーバー側でJSONとして解析できないことがあります。APIで値が取れないときは、まず「本文の形式」と「Content-Type」が一致しているかを確認しましょう。
1-2. リクエストとレスポンスの両方で使われる
Content-Typeは、リクエストだけでなくレスポンスでも使われます。クライアントが送る本文の形式を示す場合もあれば、サーバーが返す本文の形式を示す場合もあります。
リクエストでは、たとえばフロントエンドがAPIへJSONを送るときに Content-Type: application/json を付けます。レスポンスでは、APIサーバーがJSONを返すときに Content-Type: application/json を付けます。ブラウザやHTTPクライアントは、このヘッダーを見て本文をどう扱うかを判断します。
Content-Type: application/json
この例は、本文がJSON形式であることを示しています。注意点は、同じヘッダー名でも、リクエストでは「送る本文」、レスポンスでは「返ってきた本文」を表すことです。どちらのContent-Typeを見ているのかを分けて考えると、APIの切り分けがしやすくなります。
1-3. Content-Typeが合わないと何が起きるか
Content-Typeが本文の形式と合っていないと、受け取る側がデータを正しく解析できないことがあります。これはJSON送信でよく詰まる原因です。
たとえば、本文にはJSON文字列を送っているのに、Content-Typeが application/json ではない場合、サーバー側のJSONパーサーが動かないことがあります。その結果、リクエストボディが空に見えたり、期待したフィールドが取得できなかったりします。逆に、フォームデータを送っているのにJSONとして扱おうとして失敗することもあります。
よくある症状は、「Postmanでは動くのにfetchでは動かない」「サーバー側で req.body が空になる」「ファイルアップロードだけ失敗する」といったものです。この場合、URLやHTTPメソッドだけでなく、本文の形式、Content-Type、サーバー側のパーサー設定をセットで確認する必要があります。
2. application/jsonの基本
2-1. JSONを送るときのContent-Type
APIへJSONを送るときは、Content-Type: application/json を指定するのが基本です。これにより、サーバー側は本文をJSONとして解析すべきだと判断できます。
JSONは、オブジェクトや配列をテキスト形式で表すデータ形式です。フロントエンドからAPIへ登録情報や検索条件を送るときによく使われます。ただし、JavaScriptのオブジェクトそのものを送るわけではなく、HTTPの本文として送れる文字列に変換して送ります。その本文がJSONであることを伝えるのが application/json です。
fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: '山田太郎',
email: 'taro@example.com'
})
});
この例では、本文をJSON文字列に変換し、Content-TypeでJSON形式であることを伝えています。注意点は、ヘッダーだけ付けても本文がJSONになるわけではないことです。JavaScriptのオブジェクトは JSON.stringify で文字列化して送る必要があります。
2-2. JSON.stringifyが必要になる理由
fetchでJSONを送るときに JSON.stringify が必要なのは、HTTPの本文として送るにはJavaScriptオブジェクトをJSON文字列へ変換する必要があるからです。
JavaScriptのオブジェクトは、プログラム内部のデータ構造です。一方、HTTPで送る本文は文字列やバイナリとして送られます。そのため、{ name: '山田太郎' } のようなオブジェクトをそのままbodyに入れても、サーバーが期待するJSON本文にはなりません。JSON.stringify は、オブジェクトを {"name":"山田太郎"} のようなJSON文字列に変換します。
const user = {
name: '山田太郎',
email: 'taro@example.com'
};
const jsonBody = JSON.stringify(user);
このコードでは、JavaScriptオブジェクトをJSON文字列へ変換しています。注意点は、Content-Type: application/json と JSON.stringify はセットで考えることです。どちらか片方だけでは、サーバー側が期待どおりに読めないことがあります。
2-3. サーバー側でJSONとして読めない典型例
JSONを送っているつもりでも、ヘッダー、本文、サーバー側の受け取り設定のどれかがずれると読めません。原因はクライアント側だけとは限りません。
よくあるのは、Content-Typeを付け忘れている、JSON.stringify を忘れている、サーバー側でJSONボディを解析するミドルウェアが有効になっていない、というパターンです。たとえばExpressなら express.json() のような設定がないと、JSON本文を期待どおりに扱えないことがあります。
// Expressの例
app.use(express.json());
app.post('/api/users', (req, res) => {
console.log(req.body);
res.json({ ok: true });
});
この例では、JSONリクエストボディを解析する設定を有効にしています。注意点は、サーバー側のフレームワークによって必要な設定が違うことです。JSONが読めないときは、クライアントのContent-Typeだけでなく、サーバー側がその形式を受け取れる状態かも確認しましょう。
3. フォーム送信のContent-Type
3-1. application/x-www-form-urlencodedとは
application/x-www-form-urlencoded は、フォームの値をキーと値の組み合わせとして送る形式です。昔からあるHTMLフォーム送信や、一部のAPI連携で使われます。
この形式では、本文が name=山田太郎&email=taro%40example.com のような形になります。URLのクエリパラメータに似た形式で、キーと値を = で結び、複数の項目を & でつなぎます。シンプルなテキスト項目だけを送る場合には扱いやすい形式です。
fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
email: 'taro@example.com',
password: 'password'
})
});
この例では、フォーム形式でログイン情報を送っています。注意点は、JSONとは本文の形がまったく違うことです。サーバー側がJSONを期待しているAPIへこの形式で送ると、値が正しく読めない可能性があります。
3-2. multipart/form-dataとは
multipart/form-data は、複数のパートに分けてデータを送るフォーム形式です。特にファイルアップロードを含むフォームでよく使われます。
テキスト項目だけでなく、画像やPDFなどのファイルを一緒に送る必要がある場合、単純なJSONやURLエンコード形式では扱いにくくなります。multipart/form-dataでは、各項目を区切りながら送れるため、ファイルと通常のフォーム項目をまとめて送信できます。この区切りにはboundaryという情報が使われます。
const formData = new FormData();
formData.append('title', 'プロフィール画像');
formData.append('file', fileInput.files[0]);
fetch('/api/upload', {
method: 'POST',
body: formData
});
この例では、FormData を使ってファイルとテキストを送っています。注意点は、ブラウザのfetchでFormDataを送る場合、通常は Content-Type を手動で指定しないことです。手動で指定するとboundaryが正しく付かず、サーバー側で解析できない原因になります。
3-3. ファイルアップロードでmultipart/form-dataを使う理由
ファイルアップロードでは、ファイルのバイナリデータとフォーム項目を一緒に扱いやすいため、multipart/form-dataを使います。JSONだけではファイル送信に向きません。
JSONはテキストベースのデータ形式なので、ファイルそのものを自然に含める形式ではありません。Base64文字列に変換してJSONに入れる方法もありますが、データ量が増えたり、処理が複雑になったりします。通常のWebフォームやAPIでファイルを送るなら、FormDataとmultipart/form-dataを使うほうが自然です。
たとえば、プロフィール編集で「表示名」と「アイコン画像」を同時に送る場合、multipart/form-dataが向いています。落とし穴は、JSON送信と同じ感覚で Content-Type: application/json を付けてしまうことです。ファイルアップロードでは、送る本文の形式に合わせて考える必要があります。
4. Content-TypeとAcceptの違い
4-1. Content-Typeは「送る中身」の形式
Content-Typeは、自分が送る本文、または相手が返す本文の形式を示すヘッダーです。APIリクエストでは「このリクエスト本文はJSONです」のように伝えるために使います。
たとえば、POSTやPUTでリクエストボディを送る場合、サーバーはContent-Typeを見て本文の解析方法を決めます。JSONならJSONパーサー、フォームならフォームパーサー、multipartならファイルアップロード用の処理、というように分かれます。つまり、Content-Typeは本文がある通信で特に重要になります。
Content-Type: application/json
このヘッダーは、「送っている本文はJSON形式です」という意味になります。注意点は、GETリクエストのように本文を送らない通信では、リクエスト側のContent-Typeが必ず必要とは限らないことです。本文があるかどうかとセットで考えましょう。
4-2. Acceptは「受け取りたい形式」の希望
Acceptは、クライアントがサーバーから受け取りたいレスポンス形式を伝えるヘッダーです。Content-Typeとは向きが違います。
たとえば、クライアントが Accept: application/json を送ると、「できればJSONで返してください」という希望を伝えます。一方、サーバーが実際に返す形式は、レスポンスの Content-Type で示されます。つまり、Acceptはリクエスト時の希望、Content-Typeは実際の本文の形式です。
Accept: application/json
この例では、JSONレスポンスを希望しています。注意点は、Acceptを付けたからといって、必ずJSONが返るとは限らないことです。サーバーが対応していなければHTMLや別形式が返ることもあるため、レスポンスのContent-Typeも確認する必要があります。
4-3. API連携で両方を見るべき場面
API連携でうまくいかないときは、リクエストのContent-TypeとAccept、レスポンスのContent-Typeを分けて確認することが大切です。どれか1つだけ見ても原因が分からないことがあります。
たとえば、JSONを送るPOSTでは、リクエストのContent-Typeが application/json になっているかを確認します。さらに、JSONで返してほしいならAcceptも確認します。そして、実際に返ってきたレスポンスがJSONなのかHTMLなのかをレスポンスのContent-Typeで確認します。特にエラー時にHTMLのエラーページが返っていると、フロント側のJSON解析で失敗することがあります。
よくあるのは、フロント側で response.json() を呼んだらエラーになるケースです。この場合、APIがJSONではなくHTMLを返している可能性があります。DevToolsやPostmanでレスポンスヘッダーを見て、実際のContent-Typeを確認しましょう。
5. よくある落とし穴
5-1. JSONなのにContent-Typeを付けていない
JSON送信でよくある落とし穴は、本文はJSONのつもりなのにContent-Typeを付けていないことです。これにより、サーバー側がJSONとして解析できない場合があります。
特にfetchでは、bodyにJSON文字列を入れただけでは、自動で常に application/json が付くとは限りません。サーバー側はContent-Typeを見て解析処理を切り替えることがあるため、ヘッダーがないと期待どおりに読めないことがあります。つまり、JSON送信では本文の文字列化とヘッダー指定の両方が必要です。
fetch('/api/users', {
method: 'POST',
body: JSON.stringify({ name: '山田太郎' })
});
この例では、本文はJSON文字列ですが、Content-Typeが指定されていません。注意点は、サーバーやフレームワークによってはこれでも動く場合があることです。しかし、安定したAPI連携にするなら、JSON送信時は明示的に Content-Type: application/json を付けるほうが安全です。
5-2. FormData使用時にContent-Typeを手動で壊してしまう
FormDataを使うときの典型的な失敗は、Content-Type: multipart/form-data を手動で指定してしまうことです。これは一見正しそうですが、boundaryが欠けて壊れることがあります。
multipart/form-dataでは、各パートを区切るためのboundaryが必要です。ブラウザはFormDataをbodyに指定すると、適切なContent-Typeとboundaryを自動で付けます。ところが開発者が手動で multipart/form-data だけ指定すると、必要なboundaryが付かず、サーバー側でどこからどこまでが1つの項目か判断できないことがあります。
// 避けたい例
fetch('/api/upload', {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data'
},
body: formData
});
この例では、Content-Typeを手動指定しているため、boundaryが正しく設定されない可能性があります。注意点は、FormDataを使う場合は基本的にheadersのContent-Typeを省き、ブラウザやHTTPクライアントに任せることです。axiosでも同様に、環境や使い方によって自動設定に任せるほうが安全な場面があります。
5-3. レスポンスのContent-Typeが想定と違う
JSON解析で失敗するときは、レスポンスのContent-Typeが本当にJSONになっているかを確認する必要があります。リクエストが正しくても、返ってきた内容が想定外の形式ということがあります。
たとえば、APIがエラーになったときに、JSONではなくHTMLのエラーページを返す場合があります。この状態でフロント側が response.json() を実行すると、JSONとして解析できずエラーになります。つまり、「JSONが送れない」と思っていた問題が、実は「JSONが返っていない」問題であることもあります。
const response = await fetch('/api/users');
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.includes('application/json')) {
const data = await response.json();
console.log(data);
} else {
const text = await response.text();
console.log(text);
}
このコードでは、レスポンスのContent-Typeを確認してからJSONとして読むかを判断しています。注意点は、通常の業務コードですべての箇所にこの分岐を書くというより、デバッグ時に「実際に何が返っているか」を確認する考え方として役立つことです。
6. まとめ
6-1. Content-Typeの基本整理
Content-Typeは、HTTP本文の形式を相手に伝えるためのヘッダーです。JSON、フォーム、ファイルアップロードでは本文の形が違うため、適切なContent-Typeも変わります。
JSONを送るなら application/json と JSON.stringify をセットで考えます。通常のフォーム送信では application/x-www-form-urlencoded、ファイルを含む送信では multipart/form-data を使います。ただし、FormDataではContent-Typeを手動指定せず、boundaryを含めた設定をブラウザに任せるのが基本です。
また、Content-TypeとAcceptは同じ意味ではありません。Content-Typeは本文の形式、Acceptは受け取りたい形式の希望です。API連携で詰まったときは、リクエストとレスポンスの両方のヘッダーを確認しましょう。
6-2. JSONが送れないときの確認チェックリスト
JSON送信やフォーム送信で詰まったときは、本文の形式、Content-Type、サーバー側の解析設定を順番に確認すると原因を絞りやすくなります。
- JSON送信時に
Content-Type: application/jsonを付けているか - JavaScriptオブジェクトを
JSON.stringifyしているか - サーバー側でJSONボディを解析する設定があるか
- フォーム送信とJSON送信を混同していないか
- ファイルアップロードでFormDataを使っているか
- FormData使用時にContent-Typeを手動指定していないか
- AcceptとContent-Typeを同じ意味として扱っていないか
- レスポンスのContent-Typeが本当にJSONになっているか
Content-Typeの問題は、エラーメッセージだけでは分かりにくいことがあります。DevTools、Postman、サーバーログを使って、実際に送っているヘッダーと本文、返ってきたContent-Typeを確認すると、JSONがうまく送れない原因を切り分けやすくなります。
7. 参考リンク
- MDN: Content-Type
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type - MDN: Accept
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept - MDN: FormData
https://developer.mozilla.org/en-US/docs/Web/API/FormData - MDN: Fetch API
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API