You type cloudlearn.dev into a browser, hit enter, and a fraction of a second later a fully rendered page is on your screen. It feels like one instant action. It is not. Behind that single keystroke, your computer finds a server it has never met, opens a secure connection across the planet, asks a precise question, and reads back a precise answer, all before the page paints.
Every backend engineer lives inside this exchange. APIs, web apps, mobile clients, webhooks, they all speak HTTP, the language of requests and responses. If you understand what travels over the wire and why, you stop guessing when something breaks and start reasoning. This article walks the full journey from URL to response, then breaks down the request itself: methods, status codes, headers, and the two properties that quietly govern correct API design, safety and idempotency.
Who this is for
Anyone who writes or calls APIs and wants the mental model underneath them, new backend engineers, frontend devs tired of treating the network as magic, and anyone prepping for interviews. No networking background needed. If you have ever sent a `fetch` or seen a `404`, you are ready.
Ordering something by mail
HTTP is a request-response protocol: a client asks for something, a server answers. Everything else is detail.
The cleanest mental model is ordering a product by mail. You do not walk to the warehouse; you send a letter to an address, the letter contains a clear request, and some time later a reply arrives. The web works exactly this way, the pieces just have different names.
The recipient's postal addressDNS resolves a domain to an IP address
Sealing the envelope so nobody can read it in transitTLS encrypts the connection (the 's' in https)
The order form: what you want, your detailsThe HTTP request: method, path, headers, body
"Please send me one" vs "please charge my card"GET (read) vs POST (change something)
The reply: the item, or a note saying why notThe HTTP response: status code + body
The whole protocol is a letter exchange with strict formatting rules.
The journey of one request
Here is the path a single https:// request travels before your code ever runs. Each hop solves one problem: *where is the server, can we talk securely, and what exactly do you want?*
From URL to response: the four things that must happen before you get bytes back.
1
DNS: turn the name into an address
Computers route by IP, not by name. The browser asks a DNS resolver "what is the IP for cloudlearn.dev?" and gets back something like `203.0.113.42`. This answer is cached at multiple layers, which is why the first request to a new domain feels slightly slower.
2
TCP: open a reliable connection
The browser opens a TCP connection to that IP via a three-way handshake (SYN, SYN-ACK, ACK). TCP guarantees bytes arrive in order and nothing is lost, the plumbing HTTP rides on.
3
TLS: make it private
For `https://`, a TLS handshake follows: the server presents a certificate, both sides agree on encryption keys, and from here every byte is encrypted. This is what the padlock means, not "this site is good", just "nobody in the middle can read or tamper with this".
4
HTTP request: ask the precise question
Now the browser sends the actual HTTP request: a method (GET), a path (/), headers, and maybe a body. The server routes it to the right handler, your code, which builds a response.
5
HTTP response: read the answer
The server replies with a status code (200), response headers, and a body (the HTML, or JSON). The browser reads it and renders. On HTTP/1.1 keep-alive and HTTP/2, the same connection is reused for the next request, so steps 1–3 are paid once, not per request.
Anatomy of a request and response
Strip away the encryption and an HTTP request is just text in a fixed shape: a start line (method + path + version), a block of headers (key: value metadata), a blank line, then an optional body. The response mirrors it: a status line, headers, blank line, body.
Headers are the metadata that make the same body mean different things. Content-Type says how to read the body (application/json vs text/html). Authorization carries the token that proves who you are. Accept tells the server which formats you can handle. Cache-Control governs whether the answer can be reused. They are easy to ignore and responsible for a huge share of "it works in Postman but not in the browser" bugs.
HTTP methods: the verbs
The method (or verb) declares your intent. A well-behaved API maps verbs to operations consistently, because clients, caches, and proxies all make assumptions based on the method. Two properties matter most:
Safe, the request does not change server state. It is a read. Safe methods can be prefetched, retried, and cached freely.
Idempotent, making the request once or ten times leaves the server in the same final state. This is what makes retries safe after a flaky network.
Method
Purpose
Safe?
Idempotent?
GET
Read a resource
Yes
Yes
POST
Create a resource / trigger an action
No
No
PUT
Replace a resource at a known URL
No
Yes
PATCH
Partially update a resource
No
No
DELETE
Remove a resource
No
Yes
Safe implies idempotent, but not the reverse. PUT and DELETE change state yet are idempotent.
The subtle one is PUT vs POST. PUT /users/42 sets user 42 to exactly this body, send it twice and you get the same user 42, so it is idempotent. POST /users creates a *new* user each time, send it twice and you get two users, so it is not. DELETE /users/42 is idempotent because deleting an already-deleted resource still leaves it deleted (the second call returns 404, but the *state* is unchanged). PATCH is generally not idempotent because a partial change like "increment the counter" compounds on repeat.
Why idempotency is not academic
Networks fail mid-request. Your client sends a request, the connection drops before the response arrives, did it succeed? If the method is idempotent, just retry; worst case nothing extra happens. If it is not (like POST), a blind retry can double-charge a customer. This is why payment APIs ask for an idempotency key.
Status codes: the answer in three digits
Every response leads with a status code. You do not memorize all of them, you learn the families by their first digit, then a handful of specifics.
2xx, success.200 OK (here is your data), 201 Created (resource made, often after POST), 204 No Content (worked, nothing to return).
3xx, redirection.301 Moved Permanently, 304 Not Modified (your cached copy is still good).
4xx, you (the client) made a mistake.400 Bad Request, 401 Unauthorized (not logged in), 403 Forbidden (logged in, not allowed), 404 Not Found, 429 Too Many Requests.
5xx, the server broke.500 Internal Server Error (unhandled exception), 502 Bad Gateway, 503 Service Unavailable.
The 4xx vs 5xx split is a contract: 4xx means fix your request, 5xx means the server is at fault. Getting this right matters far beyond tidiness, monitoring, alerting, and client retry logic all key off it. Returning 200 with {"error": "..."} in the body breaks every one of those systems.
Seeing it for real
A raw HTTP request really is just text. Here is what your browser sends, made visible with curl -v, note the start line, the headers, and that an https request still shows you the plaintext HTTP underneath the TLS layer.
raw-request.sh
bash
# Send a GET and print the request + response headers
curl -v https://api.cloudlearn.dev/users/42# What curl shows it sent (the request):
> GET /users/42 HTTP/2
> Host: api.cloudlearn.dev
> Accept: application/json
> Authorization: Bearer eyJhbGci...
# What the server replied (the response):
< HTTP/2200
< content-type: application/json
< cache-control: max-age=60
<
{ "id": 42, "name": "Ada" }
From application code you rarely write that by hand, you use a client like the browser's fetch. But every field maps directly back to the raw request: the method, the headers, the body.
create-user.ts
typescript
// POST creates a resource: method, headers, and a JSON body.const res = awaitfetch("https://api.cloudlearn.dev/users", {
method: "POST",
headers: {
"Content-Type": "application/json", // how to read the body we send
Accept: "application/json", // how we want the reply formatted
Authorization: `Bearer ${token}`, // who we are
},
body: JSON.stringify({ name: "Ada" }),
});
// Branch on the status FAMILY, not just the happy path.if (res.status === 201) {
const user = await res.json();
console.log("created", user.id);
} elseif (res.status === 401) {
thrownewError("Token expired, re-authenticate");
} elseif (res.status >= 500) {
// Server fault: a retry might succeed. (Safe here because... it is a POST, // only retry if the endpoint accepts an idempotency key.)thrownewError("Server error");
}
Common mistakes that cost hours
Using GET for actions with side effects.GET /users/42/delete looks convenient, but GET is supposed to be safe, browsers prefetch links, crawlers follow them, and proxies cache them. People have wiped databases because a crawler followed every "delete" link. State changes belong in POST/PUT/PATCH/DELETE.
Returning the wrong status code.200 OK with an error in the body. 404 when the user is just unauthorized (use 401/403). 500 for a validation error that is really the client's fault (400). Wrong codes mislead monitoring and break client retry logic.
Ignoring idempotency on retries. Blindly retrying a failed POST can create duplicate orders or double charges. Retry idempotent methods freely; for POST, design an idempotency key or make the operation idempotent server-side.
Forgetting headers. Sending a JSON body without Content-Type: application/json so the server parses it as text. Or omitting Authorization and getting a confusing 401. The body is only half the request.
Confusing 401 and 403.401 Unauthorized means "I do not know who you are" (authenticate). 403 Forbidden means "I know who you are, you cannot do this" (authorize). Swapping them sends users down the wrong recovery path.
Takeaways
The whole article in seven lines
A URL becomes a page via four steps: DNS (name → IP), TCP (reliable connection), TLS (encryption), then the HTTP request/response.
HTTP is request-response: a client asks (method + path + headers + body), a server answers (status + headers + body).
Methods declare intent: GET reads, POST creates, PUT replaces, PATCH partially updates, DELETE removes.
**Safe** = no state change (GET). **Idempotent** = same result on repeat (GET, PUT, DELETE). POST and PATCH are neither.
Idempotency is what makes retries safe after a network blip, the reason POST needs idempotency keys.
Status families: 2xx success, 3xx redirect, 4xx your fault, 5xx server's fault. The 4xx/5xx split is a contract monitoring depends on.
Headers carry the metadata (`Content-Type`, `Authorization`, `Accept`) that make the same body mean different things.
Where to go next
You now know how a request travels and what it carries. The next step is designing the endpoints on the other end, turning these methods and status codes into a clean, predictable API.
Read REST API Design to turn these verbs and status codes into a coherent resource-based API.
This article covers concepts taught hands-on in the Cloud Engineer and DevOps career paths, with real terminal labs, production scenarios, and structured lessons.