Suggest an editImprove this articleRefine the answer for “How HTTP works and what an HTTP request consists of”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**HTTP** is a stateless request-response protocol for transferring data between clients and servers. An HTTP request has five parts: method, URL, HTTP version, headers, and body. ``` POST /api/todos HTTP/1.1 Host: example.com Content-Type: application/json Authorization: Bearer token {"text": "Buy milk"} ``` **Key point:** the server has no memory of prior requests. Every request must carry all the context it needs (auth token, session ID) in its own headers or body.Shown above the full answer for quick recall.Answer (EN)Image**HTTP (HyperText Transfer Protocol)** is a stateless application-layer protocol where a client sends a structured text request to a server, and the server sends back a response with a status code, headers, and an optional body. ## Theory ### TL;DR - HTTP is like mailing a letter: you write method + path + headers + body, drop it at a TCP port, and get a reply with a status code - A request has five parts: method, URL, HTTP version, headers, body - Stateless means the server forgets your previous request the moment it responds - GET for reading, POST for creating, PUT/PATCH for updating, DELETE for removing - Use HTTP/2 when you need many parallel requests (it multiplexes streams over one TCP connection) ### Quick example ```javascript // This is what happens when you call fetch() fetch('https://jsonplaceholder.typicode.com/users/1') .then(res => { console.log('Status:', res.status); // 200 console.log('Content-Type:', res.headers.get('content-type')); // application/json return res.json(); }) .then(data => console.log('Name:', data.name)); // Leanne Graham // Under the hood, the browser sends: // GET /users/1 HTTP/1.1 // Host: jsonplaceholder.typicode.com // Accept: application/json ``` When you call `fetch()`, the browser serializes those five parts into bytes, opens a TCP connection to port 443 (HTTPS), and writes them to the socket. The server reads the stream, parses it, and writes a response back. ### What an HTTP request actually looks like An HTTP/1.1 request is plain text. Here is a raw POST request: ``` POST /todos HTTP/1.1 Host: api.example.com Content-Type: application/json Authorization: Bearer eyJhbGc... Content-Length: 32 {"text": "Buy milk", "done": false} ``` Four sections, one blank line separating headers from body. That is the whole format. **Method** tells the server what to do with the resource. GET reads, POST creates, PUT replaces, PATCH updates partially, DELETE removes. The method also signals caching behavior: GET responses can be cached, POST ones cannot. **URL** is the resource address. It includes the path (`/todos`) and optionally query params (`?page=1&sort=asc`). **Headers** are key-value metadata. `Content-Type: application/json` tells the server how to parse the body. `Authorization: Bearer token` authenticates the request. `Host` is required in HTTP/1.1 because one IP can serve many domains. **Body** carries the payload. Only POST, PUT, and PATCH use it. GET and DELETE should not have a body (more on that in Common mistakes). ### How the request travels The browser resolves the domain via DNS, opens a TCP connection to port 80 (HTTP) or 443 (HTTPS), and writes the request bytes into the kernel buffer. For HTTPS, a TLS handshake comes first: the client sends a hello, the server responds with its certificate, they negotiate a cipher, and from that point all bytes are encrypted. The HTTP structure inside stays the same. On the server side, a library like http-parser (a C library used in both Node.js and nginx) reads the incoming stream and fills in the method, path, headers, and body. Then your application code takes over. ### HTTP/1.1 vs HTTP/2 HTTP/1.1 is text-based. You get one request at a time per TCP connection. If you send request A then request B on the same connection, B waits for A to finish. Browsers worked around this by opening six connections per domain, but that is still wasteful. HTTP/2 replaces the text format with binary frames and multiplexes multiple request streams over one TCP connection. A slow request does not block others. Same HTTP methods and headers, different wire format. HTTP/3 (QUIC) goes further: it drops TCP entirely in favor of UDP with per-stream loss recovery. A dropped packet stalls only its own stream, not the whole connection. ### Common mistakes **Sending a body with GET.** RFC 7231 does not forbid it, but proxies and caches may drop or ignore the body. You get zero bytes on the server and confused caching logs. In production, the CDN is usually what exposes this. The origin server processes the request fine, but after you add CloudFront or Cloudflare, the cached response starts coming back wrong and nobody connects it to the GET body. ```javascript // Wrong: body gets ignored by most servers and proxies fetch('/search', { method: 'GET', body: 'q=shoes' }); // Right: use query params fetch('/search?q=shoes'); ``` **Assuming the server remembers you.** HTTP is stateless. Every request is a blank slate. A POST adds an item to a cart; the next request arrives and the server has no idea who you are. ```javascript // Wrong: no session middleware means req.session is undefined app.post('/cart', (req, res) => { req.session.cart.push(req.body.item); // TypeError: Cannot read property 'cart' of undefined }); // Right: set up session middleware first app.use(session({ secret: 'key', resave: false, saveUninitialized: true })); ``` **Missing Content-Type on POST.** If you send a JSON body without `Content-Type: application/json`, Express (and most frameworks) will not know how to parse it. `req.body` ends up undefined. ```javascript // Wrong: server receives body as raw stream, req.body is undefined fetch('/todos', { method: 'POST', body: JSON.stringify({ text: 'Buy milk' }) // No Content-Type header }); // Right fetch('/todos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Buy milk' }) }); ``` **Skipping certificate validation in Node.js.** Setting `rejectUnauthorized: false` in development feels harmless, but the habit spreads to production. A misconfigured server becomes a man-in-the-middle attack waiting to happen. ### Real-world usage - **React**: `useEffect` + `fetch('/api/users')` sends a GET and parses the JSON response into component state - **Express**: `app.get('/profile', (req, res) => res.json(user))` responds with 200 + Content-Type: application/json - **Next.js API routes**: `export default (req, res) => res.status(201).json(data)` parses the full request including method and headers - **Apollo Client**: sends GraphQL queries as POST with an `Authorization` header and a JSON body - **AWS Lambda + API Gateway**: receives method, path, headers, and body as a parsed event object ### Follow-up questions **Q:** What is the difference between HTTP/1.1 and HTTP/2? **A:** HTTP/1.1 is text-based and handles one request per TCP connection at a time, causing head-of-line blocking. HTTP/2 uses binary frames and multiplexes many streams over one connection, so a slow request does not stall others. **Q:** What is in the request line vs headers? **A:** The request line is the first line: `METHOD /path HTTP/1.1`. Headers follow, one per line in `Name: Value` format, ending with a blank line before the body. **Q:** How does HTTPS change the request? **A:** It wraps the request in TLS. A handshake negotiates encryption keys, then all bytes including headers and body travel encrypted. The HTTP format inside is unchanged. **Q:** What do status codes 200, 201, and 204 mean? **A:** 200 OK with a body. 201 Created, usually returned after POST with a Location header pointing to the new resource. 204 No Content, typically after a successful DELETE. **Q:** What triggers a CORS preflight request? **A:** The browser sends an OPTIONS request first when the actual request is cross-origin and uses a non-simple method or a non-simple header like Authorization. The server must reply with Access-Control-Allow-Origin before the browser sends the real request. **Q:** How does HTTP/3 (QUIC) fix the head-of-line blocking that HTTP/2 still has? **A:** HTTP/2 multiplexes streams over one TCP connection, but TCP itself blocks all streams when a single packet is lost. QUIC runs over UDP and implements per-stream loss recovery, so a dropped packet stalls only its own stream. ## Examples ### Basic: reading request parts in Express ```javascript const express = require('express'); const app = express(); app.use(express.json()); // parses body when Content-Type is application/json app.post('/todos', (req, res) => { console.log('Method:', req.method); // POST console.log('Path:', req.path); // /todos console.log('Auth:', req.headers.authorization); // Bearer xyz console.log('Body:', req.body); // { text: 'Buy milk', done: false } res.status(201).json({ id: 1, ...req.body }); // Response: HTTP/1.1 201 Created // Content-Type: application/json // { "id": 1, "text": "Buy milk", "done": false } }); app.listen(3000); // Test with curl: // curl -X POST http://localhost:3000/todos \ // -H "Content-Type: application/json" \ // -H "Authorization: Bearer xyz" \ // -d '{"text":"Buy milk","done":false}' ``` Express unpacks all five parts of the request for you. The `express.json()` middleware reads the body stream, checks `Content-Type`, and parses the JSON into `req.body`. Without it, `req.body` is undefined. ### Intermediate: head-of-line blocking in practice ```javascript const https = require('https'); // Two requests on separate connections - both start immediately const req1 = https.request('https://httpbin.org/get', { method: 'GET' }, res1 => { console.log('Req1 status:', res1.statusCode); // 200, arrives fast }); const req2 = https.request('https://httpbin.org/delay/2', { method: 'GET' }, res2 => { console.log('Req2 status:', res2.statusCode); // 200, arrives after 2s }); req1.end(); req2.end(); // Node opens separate TCP connections here, so they run in parallel // On a single HTTP/1.1 connection, req2 would block req1 the whole 2s // This is why browsers opened 6 connections per domain in the HTTP/1.1 era ``` Each `https.request()` call opens its own TCP connection, so both run in parallel. On a single HTTP/1.1 connection the slow request would stall everything behind it. HTTP/2 fixes this by streaming both over one connection without blocking. ### Advanced: inspecting raw request structure with Node's http module ```javascript const http = require('http'); http.createServer((req, res) => { // req.method, req.url, req.httpVersion come from the request line console.log(`${req.method} ${req.url} HTTP/${req.httpVersion}`); // GET /api/data HTTP/1.1 // req.headers is an object with all header key-value pairs (lowercased) console.log('Host:', req.headers.host); console.log('Accept:', req.headers.accept); console.log('User-Agent:', req.headers['user-agent']); // Body must be read as a stream - it is not a string by default let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', () => { console.log('Body:', body); // raw string - use JSON.parse() if needed res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('OK'); }); }).listen(3000); ``` Node's `http` module gives you direct access to all five request parts. The body is a readable stream, not a string. That is exactly why `express.json()` exists: it handles the streaming and parsing so you get `req.body` as a plain object instead of wiring up `data` and `end` events yourself.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.