HTTPSystem DesignWebBackend

Understanding HTTP from Scratch

KM

Krishna Mohan

Author

2 Jan, 2026
12 min read

If you've ever opened DevTools, stared at the network tab, and thought "bro what is actually happening here?" — this blog is for you.

HTTP is literally everywhere. Every time you scroll Instagram, every time you hit "submit" on a form, every time you see that dreaded "CORS error" in your console — it's all HTTP under the hood.

But here's the thing: Most developers treat HTTP like black magic. They Google "how to fix CORS," copy-paste some headers, and move on. And honestly? That works... until it doesn't.

So let's change that. Let's break down HTTP from first principles, backend-style, and actually understand what's going on.


What Even Is HTTP?

Think of HTTP (Hypertext Transfer Protocol) as the language of the web. It's how your browser talks to servers, how your mobile app fetches data, how APIs communicate.

Imagine you're at a restaurant:

  • You (the client) look at the menu and tell the waiter what you want
  • The waiter takes your order to the kitchen (the server)
  • The kitchen prepares your food and sends it back via the waiter
  • You get your meal (the response)

That's basically HTTP. A client makes a request, the server processes it, and sends back a response. Simple, right?

But Wait — HTTP Is Stateless?

Here's where it gets interesting. Unlike humans, HTTP has zero memory. Every request is like meeting someone with amnesia — they don't remember your previous conversations.

Let's say you're ordering coffee:

  • Request 1: "Hi, I'd like a coffee"
  • Request 2: "Make it a large" ← The server goes "Who are you again? What coffee?"

This is why we need things like cookies and session tokens. They're like name tags you wear so the server can recognize you across requests.

Why design it this way? Because it's simple and scalable:

  • No session storage on the server = easier to build
  • Any server can handle any request = easy load balancing
  • Server crashes? No big deal, just restart it

The Client-Server Dance

Every HTTP interaction follows a predictable pattern:

  1. Client initiates: You can't just randomly push data to browsers (that'd be chaos). The client always starts the conversation.
  2. Server responds: The server processes the request and sends back data.
  3. Connection closes (or stays open, we'll get to that).

Think of it like texting — you send a message, they reply. You don't receive random messages from servers you've never contacted (unless you set up websockets, but that's a different story).

HTTP vs HTTPS: The Security Upgrade

HTTP = Sending postcards. Everyone who handles it can read it.
HTTPS = Sending sealed, encrypted letters. Only you and the recipient can read it.

HTTPS is just HTTP wrapped in TLS encryption. That little padlock in your browser? That means your data is encrypted from your device to the server. No snooping allowed.


A Quick History: How HTTP Evolved

HTTP didn't start as the sophisticated protocol it is today. Let's speedrun through the evolution:

HTTP/1.0 (1996): The Ancient Times

Every single request opened a new TCP connection. Want to load a webpage with 50 images? That's 50 separate connections. Painfully slow.

HTTP/1.1 (1997): The Upgrade

Introduced persistent connections — now you could reuse the same connection for multiple requests. Game changer!

Also added:

  • Chunked transfer encoding (send data in pieces)
  • Better caching
  • Host headers (multiple websites on one server)

HTTP/2 (2015): The Performance Boost

Big changes:

  • Multiplexing: Send multiple requests over one connection at the same time
  • Binary protocol: Faster to parse than text
  • Header compression: Stop sending the same headers repeatedly
  • Server push: Server can send resources before you ask

HTTP/3 (2022): The Modern Era

Built on QUIC (which uses UDP instead of TCP). Why?

  • Faster connection setup
  • Better handling of packet loss
  • Improved mobile performance

Anatomy of an HTTP Request

Let me show you what an actual HTTP request looks like:

GET /api/users/123 HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0
Authorization: Bearer abc123
Accept: application/json

Breaking it down:

  • GET: What you want to do (the method/verb)
  • /api/users/123: What you want (the URL path)
  • HTTP/1.1: Which version of HTTP you're speaking
  • Headers: Metadata about your request

And here's a POST request with a body:

POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 45

{"name": "Krishna", "email": "test@example.com"}

The blank line separates headers from the body. Everything after that is the data you're sending.


HTTP Responses: What Comes Back

When the server replies, it looks something like this:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 52
Cache-Control: max-age=3600

{"id": 123, "name": "Krishna", "email": "test@example.com"}

Key parts:

  • 200 OK: Status code (we'll dive deep into these)
  • Headers: Metadata about the response
  • Body: Your actual data

HTTP Headers: The Unsung Heroes

Headers are like instruction manuals attached to every request and response. They tell you everything about what's being sent and how to handle it.

Let me break them down by category:

Request Headers: What You're Sending

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)

"Hey server, I'm a Chrome browser on Mac"

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

"Here's proof I'm allowed to access this"

Accept: application/json

"I prefer JSON responses, please"

Response Headers: What You're Getting

Content-Type: application/json; charset=utf-8

"This response is JSON, encoded in UTF-8"

Content-Length: 1234

"This response is 1234 bytes long"

Set-Cookie: sessionId=abc123; HttpOnly; Secure

"Store this cookie for next time"

Security Headers: Locking Things Down

Strict-Transport-Security: max-age=31536000; includeSubDomains

"Always use HTTPS for this site, no exceptions"

X-Frame-Options: DENY

"Don't let this page be embedded in an iframe (prevents clickjacking)"

Content-Security-Policy: default-src 'self'

"Only load resources from my own domain (prevents XSS attacks)"


HTTP Methods: The Verbs of the Web

Methods tell the server what you want to do. Think of them as verbs in a sentence.

GET: "Give Me That Thing"

Read-only operation. Fetching data, loading pages, getting images.

GET /api/users/123

Important: GET requests should never modify data. They should be idempotent — calling it 100 times should have the same effect as calling it once.

POST: "Create Something New"

Used for creating new resources.

POST /api/users
Content-Type: application/json

{"name": "Krishna", "email": "test@example.com"}

Not idempotent — calling it 3 times creates 3 users.

PUT: "Replace This Completely"

Full replacement of a resource.

PUT /api/users/123
Content-Type: application/json

{"name": "Krishna Updated", "email": "new@example.com", "role": "admin"}

Idempotent — calling it 10 times results in the same final state.

PATCH: "Update Just These Fields"

Partial update — only change specific fields.

PATCH /api/users/123
Content-Type: application/json

{"email": "newemail@example.com"}

Idempotent — same result every time you call it.

DELETE: "Remove This"

Delete a resource.

DELETE /api/users/123

Idempotent — deleting something already deleted? No problem, same result.

OPTIONS: "What Can I Do Here?"

Used for CORS preflight checks. The browser asks "Hey, am I allowed to make this request?"

OPTIONS /api/users
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

CORS: The Bane of Every Developer's Existence

Ah, CORS. The error message that's made developers cry since 2014.

Why Does CORS Exist?

Imagine this scenario:

  1. You visit evil-site.com
  2. That site makes a request to your-bank.com/transfer?to=hacker&amount=10000
  3. Since you're logged into your bank, the request includes your auth cookies
  4. Money gone 💸

Same-Origin Policy prevents this. Browsers block requests to different domains unless the server explicitly allows it.

The Two Types of CORS Requests

Simple Requests (no preflight needed):

  • GET, POST, HEAD methods
  • Only "simple" headers (Accept, Content-Type with certain values)

Preflighted Requests (sends OPTIONS first):

  • PUT, DELETE, PATCH
  • Custom headers like Authorization
  • Content-Type: application/json

The CORS Flow

Let's say your React app on localhost:3000 wants to call an API on api.example.com:

Step 1: Preflight (OPTIONS request)

OPTIONS /api/users
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

Step 2: Server Response

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

Step 3: Actual Request Now the browser sends your real POST request.

Fixing CORS in Your Backend

// Express.js example
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.header('Access-Control-Allow-Credentials', 'true');
  
  if (req.method === 'OPTIONS') {
    return res.sendStatus(204);
  }
  next();
});

HTTP Status Codes: The Server's Mood Ring

Status codes are how servers communicate success, failure, or "it's complicated."

1xx: Informational (Rarely Seen)

100 Continue: "Keep sending your large upload, I'm listening"
101 Switching Protocols: "OK, let's upgrade to WebSocket"

2xx: Success! 🎉

200 OK: "Here's what you asked for"
201 Created: "I created that resource for you"
204 No Content: "Success, but I have nothing to send back" (common with DELETE)

3xx: Redirection

301 Moved Permanently: "That resource moved forever, update your bookmarks"
302 Found: "Temporarily moved, come back to this URL later"
304 Not Modified: "Your cached version is still good, use that"

4xx: You Messed Up

400 Bad Request: "Your request makes no sense"
401 Unauthorized: "Who are you? Log in first"
403 Forbidden: "I know who you are, but you can't do this"
404 Not Found: "That doesn't exist here"
409 Conflict: "Your request conflicts with current state" (e.g., duplicate email)
429 Too Many Requests: "Slow down! You're rate limited"

5xx: Server Messed Up

500 Internal Server Error: "I crashed, sorry"
502 Bad Gateway: "The upstream server gave me garbage"
503 Service Unavailable: "I'm overloaded or down for maintenance"
504 Gateway Timeout: "The upstream server didn't respond in time"


HTTP Caching: Making Things Fast

Caching is like keeping leftovers in the fridge instead of cooking from scratch every time.

Cache-Control: The Boss Header

Cache-Control: max-age=3600

"This response is fresh for 1 hour, don't bother asking again"

Cache-Control: no-cache

"You can cache this, but check with me before using it"

Cache-Control: no-store

"Don't cache this at all" (used for sensitive data)

ETag: The Fingerprint

ETag: "33a64df551425fcc55e4d42a148795d9"

When you request again, send:

If-None-Match: "33a64df551425fcc55e4d42a148795d9"

If nothing changed, server sends:

HTTP/1.1 304 Not Modified

No body, just "use your cached version."

Last-Modified: The Timestamp Approach

Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

Next request:

If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT

If not modified, 304 response.


Content Negotiation: "How Do You Want This?"

Clients can tell servers their preferences:

Accept: application/json

"I prefer JSON"

Accept: application/xml, application/json;q=0.9

"I prefer XML, but JSON is OK too (with 0.9 quality score)"

Accept-Language: en-US, en;q=0.9, es;q=0.8

"English (US) is best, fallback to English, then Spanish"

Accept-Encoding: gzip, deflate, br

"Compress the response, I support gzip, deflate, and Brotli"

Server responds with:

Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Content-Language: en-US

Persistent Connections: Keeping the Line Open

In HTTP/1.0, every request opened a new TCP connection. Expensive!

HTTP/1.1 introduced persistent connections:

Connection: keep-alive

Now multiple requests can share one connection:

[Client] -------- [Connection established] -------- [Server]
[Client] -- GET /page.html -->
         <-- 200 OK, HTML --
[Client] -- GET /style.css -->
         <-- 200 OK, CSS --
[Client] -- GET /script.js -->
         <-- 200 OK, JS --
[Client] -------- [Connection still alive] -------- [Server]

Much faster! No repeated handshakes.

To close:

Connection: close

Handling Large Files: Streaming and Chunking

Chunked Transfer Encoding

Server doesn't know the full size yet? Send in chunks:

HTTP/1.1 200 OK
Transfer-Encoding: chunked

5
Hello
6
 World
0

Each chunk starts with its size in hex.

Multipart Uploads

Uploading a file with form data:

POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="title"

My Photo
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="photo.jpg"
Content-Type: image/jpeg

[binary image data here]
------WebKitFormBoundary7MA4YWxkTrZu0gW--

HTTPS and TLS: Locking It Down

Why HTTPS?

Without HTTPS:

  • ISPs can see everything you browse
  • Coffee shop WiFi owners can snoop your passwords
  • Hackers can intercept and modify data

How TLS Works (Simplified)

  1. Client Hello: "Hey, I want to connect securely"
  2. Server Hello: "Here's my certificate (proof of identity)"
  3. Client verifies certificate: Checks it's signed by a trusted authority
  4. Key exchange: Both sides agree on encryption keys
  5. Encrypted communication begins 🔒

All data is now encrypted end-to-end.

TLS 1.3 is the current standard — faster and more secure than older versions.


Real-World Tips for Developers

1. Use the Right Status Codes

Don't return 200 OK with {"error": "not found"} in the body. Use 404 Not Found.

2. Cache Aggressively (But Smartly)

Static assets (images, CSS, JS) should have long cache times:

Cache-Control: public, max-age=31536000, immutable

API responses? Shorter or no caching:

Cache-Control: private, max-age=60

3. Use Compression

Enable gzip or Brotli compression. A 1MB response can become 200KB.

4. Implement Rate Limiting

Return 429 Too Many Requests when clients abuse your API:

HTTP/1.1 429 Too Many Requests
Retry-After: 60

5. Set Security Headers

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'

Wrapping Up

HTTP might seem like a boring plumbing protocol, but it's the foundation of everything web-related. Understanding it deeply makes you a better backend developer, a smarter frontend engineer, and way better at debugging those weird production issues.

Key takeaways:

  • HTTP is stateless — every request is independent
  • Methods define intent (GET, POST, PUT, DELETE, etc.)
  • Status codes communicate outcomes clearly
  • Headers carry crucial metadata and instructions
  • CORS protects users but requires server cooperation
  • Caching makes the web faster (when done right)
  • HTTPS/TLS keeps data secure

Now go build something awesome — and when that CORS error pops up, you'll know exactly what to do. 😎