Skip to content

Lesson 2 of 9 · 20m

HTTP Without a Framework

Method, path, headers, status, body — the five things every request is, before Express adds magic.

Every HTTP server on Earth does the same five things: it reads the method and path off the request line, scans the headers, reads the body, and writes back a status and a body. Frameworks wrap that in routing tables and middleware — but peel any of them back and the machinery is identical. Write one by hand once and the magic disappears.

A real server, no framework

import http from "node:http";

const server = http.createServer((req, res) => {
  if (req.method === "GET" && req.url === "/health") {
    res.writeHead(200, { "content-type": "application/json" });
    res.end(JSON.stringify({ ok: true }));
    return;
  }
  res.writeHead(404, { "content-type": "application/json" });
  res.end(JSON.stringify({ error: "not found" }));
});

server.listen(3000);

That is a complete HTTP server. req.method and req.url are the routing decision. res.writeHead(status, headers) sets the response metadata. res.end(body) sends it. Everything Express, Hono, or Fastify does is sugar on top of this loop.

Reading the body is a stream

let body = "";
for await (const chunk of req) body += chunk;
const data = JSON.parse(body || "{}");

Requests are streams — the body arrives in chunks. Frameworks hide this behind req.body or await req.json(), but the stream is what they are consuming.

Try it yourself — the routing decision, live

const req = { method: "GET", url: "/users/42" };
const [route, id] = req.url.split("/").slice(1);
console.log("method:", req.method, "| route:", route, "| param:", id);

Why it matters

When your framework's route does something weird, the answer is always in these five fields — method, path, headers, body, status. The framework is a lookup table on top; the protocol is underneath.