Suggest an editImprove this articleRefine the answer for “How does the HTTP module work in Node.js?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`http`** is a built-in Node.js module for creating HTTP servers and making outbound requests. `http.createServer()` receives a callback with `req` (readable stream) and `res` (writable stream). Call `res.writeHead()` for status and headers, then `res.end()` to send the body. ```js const http = require('http'); const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true })); }); server.listen(3000); ``` **Key point:** `req` and `res` are streams. The request body is not available immediately. You collect it from `data` events.Shown above the full answer for quick recall.Answer (EN)Image**`http`** is a built-in Node.js module that lets you create HTTP servers and make outbound HTTP requests, with no npm install required. ## Theory ### TL;DR - Every HTTP server in Node runs in a single process but handles many connections through the event loop - `req` is an `IncomingMessage` (readable stream), `res` is a `ServerResponse` (writable stream) - The request body does not arrive all at once. You collect chunks via `data` events - `http.createServer()` returns a `net.Server` under the hood. Express wraps this same function - Use `https` for production (TLS) and `http2` when you need multiplexing ### Quick example ```js const http = require('http'); const server = http.createServer((req, res) => { // req = IncomingMessage (readable stream) // res = ServerResponse (writable stream) res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message: 'Hello' })); }); server.listen(3000, () => console.log('Running on port 3000')); ``` The callback fires on every incoming request. `res.end()` flushes the response and signals that you are done writing. ### How the request-response cycle works When a client connects, Node's TCP layer accepts the connection and starts parsing the HTTP message. Once the headers are parsed, Node fires your `createServer` callback with `req` and `res`. The body has not been fully read yet. It is still streaming in. That is why you listen for `data` and `end` events manually. `res.writeHead()` writes the status line and headers. `res.end()` sends the body and closes the response. Forgetting `res.end()` is one of those bugs you hit once when a curl request just hangs and you spend ten minutes wondering why. ### Reading the request object ```js const server = http.createServer((req, res) => { console.log(req.method); // 'GET', 'POST', etc. console.log(req.url); // '/api/users?page=2' console.log(req.headers); // { host: 'localhost:3000', ... } const url = new URL(req.url, `http://${req.headers.host}`); console.log(url.pathname); // '/api/users' console.log(url.searchParams.get('page')); // '2' res.end('ok'); }); ``` `req.url` is a raw string. Use the built-in `URL` constructor to parse query parameters reliably. String splits break the moment a query string appears. ### Collecting a POST body The body arrives in chunks. You assemble them yourself: ```js const server = http.createServer((req, res) => { if (req.method === 'POST' && req.url === '/api/users') { let body = ''; req.on('data', (chunk) => { body += chunk.toString(); }); req.on('end', () => { const user = JSON.parse(body); res.writeHead(201, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ created: user })); }); } }); ``` There is no body size limit by default. A client can stream gigabytes at you. In production, always cap body size. Express does this automatically through `bodyParser`. ### Making outbound requests ```js const http = require('http'); // GET http.get('http://api.example.com/data', (res) => { let data = ''; res.on('data', (chunk) => (data += chunk)); res.on('end', () => console.log(JSON.parse(data))); }); // POST const payload = JSON.stringify({ name: 'Alice' }); const req = http.request( { hostname: 'api.example.com', port: 80, path: '/users', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload), }, }, (res) => { let data = ''; res.on('data', (chunk) => (data += chunk)); res.on('end', () => console.log(JSON.parse(data))); } ); req.write(payload); req.end(); ``` The callback in `http.request()` is shorthand for the `response` event. Without `req.end()` the request never sends. ### http vs https vs http2 | Module | Protocol | When to use | |--------|----------|-------------| | `http` | HTTP/1.1 | Local dev, internal services on a private network | | `https` | HTTPS + TLS | Any production server that handles real users | | `http2` | HTTP/2 | When you need multiplexing or server push | `https` requires TLS certificates. `http2` uses a different API (`http2.createSecureServer`) and supports multiplexed streams over a single TCP connection. ### http vs Express | Feature | Raw `http` | Express.js | |---------|------------|------------| | Routing | Manual `if/else` on `req.url` | `app.get('/path', handler)` | | Body parsing | Manual stream collection | `express.json()` middleware | | Middleware | Not built in | Supported out of the box | | Static files | Manual | `express.static()` | Express calls `http.createServer()` internally. When you write `app.listen(3000)`, Express runs `http.createServer(app).listen(3000)` behind the scenes. You are always using the `http` module. Express just gives you a better API on top. ### Common mistakes **1. Forgetting `res.end()`** ```js // Broken - client hangs indefinitely http.createServer((req, res) => { res.writeHead(200); // res.end() is missing }); ``` The response stream never closes. The client waits until timeout. **2. Writing headers after the body has started** ```js // Broken http.createServer((req, res) => { res.write('some data'); res.writeHead(200); // Error: Cannot set headers after they are sent }); ``` `res.writeHead()` must come before any `res.write()` call. **3. No error handler on `req`** ```js // Fragile - process will crash if client disconnects early req.on('data', (chunk) => { body += chunk; }); // Correct req.on('data', (chunk) => { body += chunk; }); req.on('error', (err) => { res.writeHead(400); res.end(err.message); }); ``` If the client disconnects mid-request, Node emits an `error` event on `req`. Without a handler, the process crashes. **4. No body size limit** ```js let body = ''; req.on('data', (chunk) => { body += chunk; if (body.length > 1e6) { // 1MB cap req.destroy(); res.writeHead(413); res.end('Payload too large'); } }); ``` **5. Parsing `req.url` with string splits** ```js // Breaks with query strings like /users/42?format=json const id = req.url.split('/')[2]; // Reliable const url = new URL(req.url, `http://${req.headers.host}`); const id = url.pathname.split('/')[2]; ``` ### Where this appears in real codebases - Express and Fastify both call `http.createServer()` under the hood - Next.js custom servers use it directly - Lightweight Node microservices that skip full frameworks - `http.get()` and `http.request()` appear in older code written before `fetch` became available in Node 18 ### Follow-up questions **Q:** Why does Express use `http.createServer()` instead of its own TCP layer? **A:** Because `http` is Node's built-in TCP parser and HTTP message handler. Every Node HTTP framework has to go through it. Express adds routing, middleware chains, and better request/response helpers on top of the same callback. **Q:** What is `IncomingMessage` and why does it extend a readable stream? **A:** `IncomingMessage` represents the incoming HTTP message. It extends `stream.Readable` because the body can be arbitrarily large. Reading it as a stream avoids loading the whole payload into memory at once. **Q:** How does Node handle concurrent requests if it is single-threaded? **A:** The event loop handles I/O asynchronously. While one request waits for a database query, the loop picks up other incoming connections. No threads are needed for I/O-bound work. **Q:** What happens if you call `res.end()` twice? **A:** Node throws `Error: write after end`. The second call tries to write to an already-closed stream. **Q:** How would you protect an `http` server from clients sending oversized bodies? **A:** Set a size limit inside the `data` handler and call `req.destroy()` when the threshold is exceeded. Also set `server.timeout` to close idle connections automatically. ## Examples ### Basic JSON API endpoint ```js const http = require('http'); const users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, ]; const server = http.createServer((req, res) => { if (req.method === 'GET' && req.url === '/api/users') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(users)); return; } res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Not found' })); }); server.listen(3000); ``` A GET to `/api/users` returns the user list. Everything else gets a 404. This is the same pattern Express routes use, without the abstraction layer. ### Creating a user with POST body handling ```js const http = require('http'); const server = http.createServer((req, res) => { if (req.method !== 'POST' || req.url !== '/api/users') { res.writeHead(404); res.end(); return; } let body = ''; req.on('data', (chunk) => { body += chunk.toString(); if (body.length > 1e5) { // 100KB guard req.destroy(); } }); req.on('end', () => { try { const user = JSON.parse(body); res.writeHead(201, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ id: Date.now(), ...user })); } catch { res.writeHead(400); res.end(JSON.stringify({ error: 'Invalid JSON' })); } }); req.on('error', () => { res.writeHead(400); res.end(); }); }); server.listen(3000); ``` This is the full pattern you would need in production: a body size guard, JSON parse error handling, and an error listener on the request stream. ### Proxying a request to an internal service ```js const http = require('http'); const server = http.createServer((req, res) => { const proxy = http.request( { hostname: 'internal-service', port: 8080, path: req.url, method: req.method, headers: req.headers, }, (proxyRes) => { res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); // stream response directly back to the client } ); req.pipe(proxy); // stream request body to the internal service proxy.on('error', () => { res.writeHead(502); res.end('Bad gateway'); }); }); server.listen(3000); ``` Piping `req` directly into the proxy request avoids buffering the body in memory. The response streams back the same way. This is the pattern behind HTTP reverse proxies.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.