The http Module & Creating Servers

📖 Concept

The http module is Node.js's built-in module for creating HTTP servers and making HTTP requests. Every web framework (Express, Koa, Fastify) is built on top of it.

Creating a basic server:

const http = require('http');
const server = http.createServer((req, res) => {
  res.end('Hello, World!');
});
server.listen(3000);

The request/response lifecycle:

  1. Client sends an HTTP request (method, URL, headers, body)
  2. Node.js's http.Server emits a 'request' event
  3. Your callback receives req (IncomingMessage) and res (ServerResponse)
  4. You read from req (request data) and write to res (response data)
  5. Call res.end() to finish the response

req (IncomingMessage) — what the client sent:

  • req.method — GET, POST, PUT, DELETE, PATCH
  • req.url — The request URL path (/users?page=2)
  • req.headers — Request headers (lowercase keys)
  • req is a Readable stream — body data arrives in chunks

res (ServerResponse) — what you send back:

  • res.statusCode = 200 — Set HTTP status code
  • res.setHeader(name, value) — Set response headers
  • res.writeHead(statusCode, headers) — Set status + headers at once
  • res.write(data) — Send response body (can call multiple times)
  • res.end(data) — End the response (required!)

HTTP status codes to know:

Code Meaning When to use
200 OK Successful GET, PUT
201 Created Successful POST (resource created)
204 No Content Successful DELETE
301 Moved Permanently URL changed permanently
400 Bad Request Invalid client request
401 Unauthorized Not authenticated
403 Forbidden Authenticated but not allowed
404 Not Found Resource doesn't exist
500 Internal Server Error Server-side bug

🏠 Real-world analogy: An HTTP server is like a restaurant. The customer (client) sends an order (request) to the kitchen (server). The kitchen reads the order (req), prepares the food (processing), and delivers the plate (res). res.end() is putting the plate on the table — the order is complete.

💻 Code Example

codeTap to expand ⛶
1// HTTP Server — Built from scratch
2
3const http = require("http");
4const url = require("url");
5
6// 1. Basic HTTP server
7const server = http.createServer((req, res) => {
8 // Parse URL and query parameters
9 const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
10 const pathname = parsedUrl.pathname;
11 const query = Object.fromEntries(parsedUrl.searchParams);
12
13 // Log request
14 console.log(`[${req.method}] ${pathname}`, query);
15
16 // Set CORS headers
17 res.setHeader("Access-Control-Allow-Origin", "*");
18 res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
19 res.setHeader("Access-Control-Allow-Headers", "Content-Type");
20
21 // Handle preflight
22 if (req.method === "OPTIONS") {
23 res.writeHead(204);
24 res.end();
25 return;
26 }
27
28 // Simple router
29 if (pathname === "/" && req.method === "GET") {
30 res.writeHead(200, { "Content-Type": "application/json" });
31 res.end(JSON.stringify({ message: "Welcome to the API", version: "1.0" }));
32 } else if (pathname === "/health" && req.method === "GET") {
33 res.writeHead(200, { "Content-Type": "application/json" });
34 res.end(JSON.stringify({ status: "healthy", uptime: process.uptime() }));
35 } else if (pathname === "/users" && req.method === "GET") {
36 const users = [
37 { id: 1, name: "Alice" },
38 { id: 2, name: "Bob" },
39 ];
40 // Support pagination via query params
41 const page = parseInt(query.page) || 1;
42 const limit = parseInt(query.limit) || 10;
43 res.writeHead(200, { "Content-Type": "application/json" });
44 res.end(JSON.stringify({ data: users, page, limit }));
45 } else if (pathname === "/users" && req.method === "POST") {
46 // Read request body (it's a stream!)
47 let body = "";
48 req.on("data", (chunk) => {
49 body += chunk;
50 // Prevent large payloads (DoS protection)
51 if (body.length > 1e6) {
52 res.writeHead(413, { "Content-Type": "application/json" });
53 res.end(JSON.stringify({ error: "Payload too large" }));
54 req.destroy();
55 }
56 });
57 req.on("end", () => {
58 try {
59 const user = JSON.parse(body);
60 // Validate
61 if (!user.name) {
62 res.writeHead(400, { "Content-Type": "application/json" });
63 res.end(JSON.stringify({ error: "Name is required" }));
64 return;
65 }
66 // Create user (mock)
67 const newUser = { id: Date.now(), ...user };
68 res.writeHead(201, { "Content-Type": "application/json" });
69 res.end(JSON.stringify(newUser));
70 } catch (err) {
71 res.writeHead(400, { "Content-Type": "application/json" });
72 res.end(JSON.stringify({ error: "Invalid JSON" }));
73 }
74 });
75 } else {
76 // 404 Not Found
77 res.writeHead(404, { "Content-Type": "application/json" });
78 res.end(JSON.stringify({ error: "Not found" }));
79 }
80});
81
82// 2. Error handling
83server.on("error", (err) => {
84 if (err.code === "EADDRINUSE") {
85 console.error(`Port ${err.port} is already in use`);
86 } else {
87 console.error("Server error:", err);
88 }
89});
90
91// 3. Start server
92const PORT = process.env.PORT || 3000;
93server.listen(PORT, () => {
94 console.log(`Server running at http://localhost:${PORT}`);
95});
96
97// 4. Graceful shutdown
98process.on("SIGTERM", () => {
99 console.log("SIGTERM received. Shutting down gracefully...");
100 server.close(() => {
101 console.log("Server closed");
102 process.exit(0);
103 });
104});

🏋️ Practice Exercise

Exercises:

  1. Build a complete HTTP server with GET, POST, PUT, DELETE routes for a "todos" resource
  2. Add query parameter parsing for pagination (?page=2&limit=10)
  3. Implement request body parsing for JSON and URL-encoded form data
  4. Add request logging middleware that logs method, URL, status code, and response time
  5. Serve static files (HTML, CSS, JS) from a public/ directory with proper MIME types
  6. Implement rate limiting — max 100 requests per minute per IP address

⚠️ Common Mistakes

  • Forgetting to call res.end() — the client hangs forever waiting for a response, eventually timing out

  • Setting headers after res.write() or res.end() — headers must be set before sending body; Node.js throws 'ERR_HTTP_HEADERS_SENT'

  • Not handling the request body as a stream — req.body doesn't exist by default; you must listen for data and end events to collect the body

  • Not setting Content-Type headers — browsers and API clients may misinterpret the response without proper content type

  • Using http.createServer() for production without a reverse proxy — always put nginx or a load balancer in front for SSL, compression, and safety

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for The http Module & Creating Servers. Login to unlock this feature.