Request and Response: What Does the Browser Actually Send?
The frontend writes:
await fetch("/api/users/42");
At the HTTP level the intent looks roughly like this:
GET /api/users/42
But a request is not a URL.
Semantically it consists of:
method
target
headers
optional content
And a response, correspondingly:
status
headers
optional content
The request
An example simplified to HTTP/1.1 notation:
GET /api/users/42 HTTP/1.1
Host: example.com
Accept: */*
Cookie: session=...
The browser can add some of the headers on its own.
JavaScript does not construct a raw network packet.
It calls a browser API:
JavaScript
|
v
Fetch
|
v
browser builds HTTP request
That distinction keeps coming back throughout the series.
The response
The backend can answer:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 24
{"id":42,"name":"Alice"}
In fetch() we first get a Response object:
const response = await fetch(url);
response.status;
response.headers;
And we consume the body separately:
const user = await response.json();
That is not an accident.
Headers and body have separate lifecycles.
A resolved fetch() does not mean 200
If the backend answers:
HTTP/1.1 404 Not Found
the browser received a valid HTTP response.
So:
const response = await fetch("/missing");
console.log(response.status); // 404
console.log(response.ok); // false
The promise does not have to be rejected.
For the Fetch API:
HTTP error response
!=
network failure
That is a very important boundary.
A 404 and a 500 are information inside HTTP.
Having no response to hand over at all is a different class of problem.
A body does not always exist
The frontend often assumes:
response = JSON
But an HTTP response may have no content.
For example:
HTTP/1.1 204 No Content
This code:
await response.json();
then has nothing to parse.
So an API client should understand the contract of the endpoint instead of treating every success as JSON automatically.
An HTTP message is an abstraction
In HTTP/1.1 a request looks on the wire roughly like the text shown above.
In HTTP/2 a request is encoded as binary frames.
In HTTP/3 it is not sent as a textual line either:
GET /api/users/42 HTTP/1.1
Yet semantically it is still:
GET
/api/users/42
headers
That is why DevTools shows us the logical HTTP request, not necessarily the bytes travelling over the network.
Which leads to the next question.
If GET, POST, 404 and 500 are part of HTTP semantics, what exactly do they mean, and why can the browser and the infrastructure behave differently depending on the method and the status?