Before you write a single route handler or call a single API, HTTP is already doing a significant amount of work underneath you. These are the bits I keep coming back to — the model, the mechanics, and the sharp edges that actually matter when things go wrong.
The Core Model
HTTP is the protocol browsers and servers use to exchange data. Communication is always initiated by the client — the server just waits, processes, and responds (with JSON, HTML, text, whatever is appropriate).
The protocol is stateless. The server holds no memory of previous interactions. Every request must be fully self-contained, carrying whatever it needs — auth tokens, session IDs — to be understood in isolation. This makes the architecture simple and horizontally scalable: any server in a cluster can handle any request without needing shared state.
Where HTTP Lives (OSI Model)
HTTP sits at Layer 7, the Application Layer. It doesn't deal with transport directly — that's TCP's job. TCP handles reliable, ordered delivery. HTTP builds its semantics on top of that. As an application developer you mostly live at Layer 7; the handshakes happen under you.
Version History
Each version solved a specific bottleneck from the one before:
- HTTP/1.0 — a new TCP connection per request. Slow.
- HTTP/1.1 — persistent connections (keep-alive). Multiple requests over one connection.
- HTTP/2 — multiplexing. Multiple requests simultaneously over one connection, plus binary framing.
- HTTP/3 — built on QUIC over UDP. Faster handshakes, better packet-loss recovery.
Anatomy of a Message
Both request and response messages share the same structural shape: a start line, headers, a blank line (CRLF), then an optional body. The blank line is load-bearing — it's the exact delimiter that tells the parser where headers end and payload begins.
GET /api/users HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>
Accept: application/json
Headers
Headers are key-value metadata — think of them as writing the shipping label on the outside of a parcel. They keep metadata cleanly separated from the payload. A few categories worth knowing:
- Request headers — client context:
User-Agent,Authorization,Accept - General headers — apply to both sides:
Date,Cache-Control,Connection - Representation headers — body format:
Content-Type,Content-Length,Content-Encoding - Security headers — browser policy:
Strict-Transport-Security,Content-Security-Policy
Methods and Idempotency
Methods encode the client's intent. The semantics matter more than people give them credit for:
GET— fetchPOST— createPATCH— partial updatePUT— full replacementDELETE— removeOPTIONS— server capabilities (used in CORS preflight)
Idempotency matters: GET, PUT, and
DELETE can be called any number of times and produce the same final state.
POST is not idempotent — call it twice, you create two resources.
Sending a PUT when you mean PATCH isn't just a style issue;
it overwrites fields the client never intended to touch.
CORS
Browsers enforce the Same-Origin Policy by default: a page at localhost:5173
cannot read a response from localhost:3000 — even on the same machine,
different ports are different origins.
CORS lets servers explicitly permit cross-origin requests. Simple requests
(plain GET or POST, no custom headers) go straight through if the server returns a
matching Access-Control-Allow-Origin. Anything more complex — PUT,
DELETE, Authorization headers, application/json
body — triggers a preflight:
- Browser automatically fires an
OPTIONSrequest first. - Server must respond
204 No ContentwithAccess-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers, andAccess-Control-Max-Age. - If approved, the actual request goes through.
A CORS error in the network tab almost always means the server didn't return the right headers — not that your API endpoint is broken.
Status Codes
The category tells you almost everything at a glance:
- 1xx — informational (e.g.,
100 Continue) - 2xx — success:
200 OK,201 Created,204 No Content - 3xx — redirection:
301permanent,302temporary,304 Not Modified - 4xx — client error:
400bad request,401unauthenticated,403unauthorized,404not found,409conflict,429rate limited - 5xx — server error:
500unhandled exception,502bad gateway,503unavailable,504timeout
Worth drawing a hard line: 401 means "I don't know who you are" (authentication). 403 means "I know who you are, but you can't do that" (authorization). Mixing these up in production is a real debugging headache.
Caching
Three headers drive HTTP caching: Cache-Control (duration),
ETag (a hash of the resource), and Last-Modified.
The client stores the ETag; on the next request it sends If-None-Match.
If the server's resource hash matches, it responds 304 Not Modified — no body,
no wasted bandwidth. A 304 is a success, not an error.
Content Negotiation and Compression
The client declares what it can handle — Accept: application/json,
Accept-Encoding: gzip, Accept-Language: en — and the server
responds with Content-Type and Content-Encoding accordingly.
Compression is not optional for performance: uncompressed large text payloads can be
multiple times heavier than gzipped equivalents. The browser decompresses automatically.
Large Payloads
File uploads use multipart/form-data. The key detail: a boundary
string defined in the headers acts as the physical delimiter between binary chunks in the
body. Large server responses use chunked transfer encoding or Server-Sent Events
(text/event-stream), streaming data while the connection stays open.
HTTPS / TLS
HTTPS is HTTP wrapped in a TLS tunnel. SSL is deprecated; TLS is the standard. Certificates authenticate the server; TLS encrypts the data in transit. Your application layer logic doesn't change — the encryption layer sits below it at the transport boundary.
HTTP is one of those things that's easy to use before you understand it — and much easier to debug once you do. The stateless model, the method semantics, the preflight dance — they all have reasons behind them. The next post covers routing: how a server takes an incoming request and maps it to the right piece of logic.