← Blog

HTTP From the Ground Up

backend

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.

[Image: Client → Request → Server → Response cycle diagram]
The fundamental HTTP exchange. Every browser interaction follows this loop.

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 — fetch
  • POST — create
  • PATCH — partial update
  • PUT — full replacement
  • DELETE — remove
  • OPTIONS — 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:

  1. Browser automatically fires an OPTIONS request first.
  2. Server must respond 204 No Content with Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, and Access-Control-Max-Age.
  3. 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: 301 permanent, 302 temporary, 304 Not Modified
  • 4xx — client error: 400 bad request, 401 unauthenticated, 403 unauthorized, 404 not found, 409 conflict, 429 rate limited
  • 5xx — server error: 500 unhandled exception, 502 bad gateway, 503 unavailable, 504 timeout

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.