Chapter 22: Node.js HTTP Module
1. What is the http module really?
node:http is the lowest-level built-in module in Node.js that lets you:
- Create HTTP servers (listen for incoming requests)
- Make HTTP client requests (talk to other servers)
It is the foundation that almost every higher-level framework (Express, Fastify, NestJS, Hono, Koa, etc.) is built on top of.
Important facts 2025–2026:
- http is purely core — no npm install needed
- It is very fast and very low overhead
- It has no middleware, no routing, no JSON parsing — you get raw incoming requests and raw responses
- Almost no one uses http directly for real applications anymore — but understanding it deeply makes you much better at using Express/Fastify/etc.
2. Creating the most basic HTTP server
Let’s start with the classic “hello world” server — but we’ll explain every line.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// 1. Import the core module (modern style) import { createServer } from 'node:http'; // 2. Create the server // The callback runs EVERY time a request arrives const server = createServer((request, response) => { // request → information about what the client sent // response → object we use to send answer back console.log('New request arrived!'); console.log('Method:', request.method); console.log('URL:', request.url); // Set status code & headers response.writeHead(200, { 'Content-Type': 'text/plain', 'X-Powered-By': 'Node.js Core HTTP' }); // Send body response.write('Hello from pure Node.js HTTP module!\n'); response.write(`You requested: {request.url}\n`); response.write(`Time: ${new Date().toLocaleString('en-IN')}`); // End the response (very important!) response.end(); }); // 3. Start listening const PORT = 3000; server.listen(PORT, () => { console.log(`Server is listening → http://localhost:${PORT}`); }); |
Run:
|
0 1 2 3 4 5 6 |
node server.js |
Open browser → http://localhost:3000
You should see:
|
0 1 2 3 4 5 6 7 8 |
Hello from pure Node.js HTTP module! You requested: / Time: 10/2/2026, 2:45:12 PM |
3. Understanding the request & response objects
request (IncomingMessage)
Important properties you use almost every time:
|
0 1 2 3 4 5 6 7 8 9 10 |
request.method // GET, POST, PUT, DELETE, PATCH... request.url // /users/123?sort=desc&page=2 request.headers // object: { host: 'localhost:3000', accept: 'text/html', ... } request.httpVersion // '1.1' or '2.0' request.socket.remoteAddress // client's IP |
Very common pattern — parsing query string
|
0 1 2 3 4 5 6 7 8 9 10 11 |
import { parse } from 'node:url'; const { pathname, query } = parse(request.url, true); console.log('Path:', pathname); // /users console.log('Query:', query); // { sort: 'desc', page: '2' } |
response (ServerResponse)
Important methods:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
response.statusCode = 201; // or use writeHead() response.setHeader('Content-Type', 'application/json'); response.writeHead(200, { 'Content-Type': 'text/html' }); response.write('some text'); // can call multiple times response.write(JSON.stringify({ ok: true })); response.end(); // MUST call this to finish response // or shortcut: response.end('Hello world'); |
4. Realistic example: JSON API server (closer to real usage)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
import { createServer } from 'node:http'; const server = createServer((req, res) => { const url = req.url; const method = req.method; // Very basic routing (real apps use Express/Fastify) if (url === '/' && method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message: 'Welcome to my tiny API', time: new Date().toISOString(), status: 'healthy' })); return; } if (url === '/users' && method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify([ { id: 1, name: 'Aman' }, { id: 2, name: 'Priya' } ])); return; } if (url === '/users' && method === 'POST') { // Important: collect body (http does NOT parse it automatically) let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', () => { try { const data = JSON.parse(body); console.log('Received:', data); res.writeHead(201, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, message: 'User created', received: data })); } catch (err) { res.writeHead(400); res.end(JSON.stringify({ error: 'Invalid JSON' })); } }); return; } // 404 fallback res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Not found' })); }); server.listen(4000, () => { console.log('API server running → http://localhost:4000'); }); |
Try with curl:
|
0 1 2 3 4 5 6 7 8 9 10 |
curl http://localhost:4000/users curl -X POST http://localhost:4000/users \ -H "Content-Type: application/json" \ -d '{"name":"Rahul","age":28}' |
5. Modern alternative: using fetch as client (Node.js 18+)
You don’t always need http.request anymore — you can use the global fetch:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
async function getTodo() { const res = await fetch('https://jsonplaceholder.typicode.com/todos/1'); if (!res.ok) { throw new Error(`HTTP error! status: {res.status}`); } const todo = await res.json(); console.log(todo); } getTodo().catch(console.error); |
Still useful to know http.request for:
- Fine-grained control (custom agents, keep-alive, timeouts…)
- HTTP/2 support
- Streaming responses
- Very low-level debugging
6. Important gotchas & best practices (2025–2026)
- Always call response.end() → if you forget → client hangs forever
- Body is streamed — you must collect chunks manually (or use express.json())
- Headers must be set before body → writeHead or setHeader before write/end
- Use node: prefix
JavaScript0123456import { createServer } from 'node:http';
- Handle errors globally
|
0 1 2 3 4 5 6 7 8 |
server.on('error', (err) => { console.error('Server error:', err.message); }); |
- Do NOT block the event loop in request handler → long synchronous work → blocks all other requests
- Real apps almost never use raw http → Express/Fastify/Hono give you:
- routing
- middleware
- JSON parsing
- error handling
- request body parsing
- much cleaner code
Summary – When to use what
| Use case | Recommended choice 2026 | Why? |
|---|---|---|
| Learning how HTTP works | Raw node:http | You understand the foundation |
| Building real APIs / websites | Express, Fastify, Hono | 100× faster development, safer |
| Very high-performance / minimal | Fastify or Hono | Very fast, low overhead |
| Microservices with strict control | Raw http + undici or node-fetch | Full control over headers, agents, timeouts |
| Making outgoing requests | fetch (built-in since Node 18) | Cleanest & modern |
Which direction would you like to go deeper next?
- Deep dive into streaming with http (file download, video, large responses)
- Making HTTP client requests with http.request (very detailed)
- Comparison: raw http vs Express vs Fastify vs Hono
- How keep-alive and HTTP/2 work in Node.js
- Error handling patterns in raw HTTP servers
Just tell me what interests you most — I’ll continue with concrete examples. 😊
