Chapter 4 of 12
Node.js can create an HTTP server from scratch using just the built-in http module — no Express, no framework. Understanding this reveals exactly what frameworks do for you.
const http = require("http");
const server = http.createServer((request, response) => {
// request — the incoming request object
console.log(`${request.method} ${request.url}`);
// Route based on URL
if (request.url === "/" && request.method === "GET") {
response.writeHead(200, { "Content-Type": "text/html" });
response.end("<h1>Hello from Node.js!</h1>");
} else if (request.url === "/api/status" && request.method === "GET") {
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify({ status: "ok", uptime: process.uptime() }));
} else {
response.writeHead(404, { "Content-Type": "application/json" });
response.end(JSON.stringify({ error: "Not found" }));
}
});
server.listen(3000, () => {
console.log("Server running at http://localhost:3000");
});ANALOGY
This is exactly what Express does. Express doesn't do magic — it wraps Node's http module and adds routing, middleware, and a nicer API. When you call app.get('/api/users', handler) in Express, it's doing exactly the URL matching and response handling you see above, just with much cleaner syntax.
const http = require("http");
const server = http.createServer((req, res) => {
if (req.url === "/api/users" && req.method === "POST") {
let body = "";
// Collect data chunks
req.on("data", chunk => {
body += chunk.toString();
});
// All chunks received — parse and respond
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, user: data }));
} catch {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Invalid JSON" }));
}
});
}
});