Headers and Body: Metadata vs Representation
A typical request:
fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
name: "Alice"
})
});
It is easy to treat Content-Type as boilerplate.
It is not.
A body is bytes, not JSON
For HTTP the body is content transferred as a sequence of bytes.
It is the header that tells the recipient how to interpret it:
Content-Type: application/json
Other possibilities:
text/plain
text/html
application/octet-stream
multipart/form-data
image/webp
HTTP does not require JSON.
JSON is a choice of the application layer.
Content-Type describes what we are sending
Content-Type: application/json
means:
the content of this message is JSON.
Whereas:
Accept: application/json
means:
the client prefers JSON as the representation of the response.
Those are two different directions of negotiation.
Content-Type -> what I am sending
Accept -> what I want back
The browser can build the body for us
With FormData:
const form = new FormData();
form.append("name", "Alice");
form.append("avatar", file);
fetch("/profile", {
method: "POST",
body: form
});
We should not set this by hand:
Content-Type: multipart/form-data
The browser has to add the correct boundary.
For example:
multipart/form-data; boundary=----browser-generated
This is a case where manual control over HTTP makes the request worse.
Content-Encoding is a different layer
A response can carry:
Content-Type: application/json
Content-Encoding: gzip
Semantically the representation is JSON.
On the wire the content was additionally encoded with compression.
The browser usually decompresses it before exposing it through the Fetch API.
The frontend gets:
await response.json();
and not compressed gzip bytes to unpack by hand.
Content-Length is not always needed
If the size of the content is known, this can appear:
Content-Length: 842
But a streaming response may be produced gradually.
The full size then does not have to be known up front.
Which matters for the frontend:
response headers available
!=
entire body downloaded
That is why Response.body can be a ReadableStream.
Headers steer the browser's behavior
Headers are not just metadata for the backend.
They can affect:
cache
cookies
content interpretation
redirects
CORS
download behavior
security policy
But JavaScript does not fully control them.
The browser is an active participant in HTTP and builds part of the request itself.
That is the subject of the next article.