Skip to main content
CodeOath
← All posts

Node.js95 min total · 14 parts

Node.js Fundamentals: The Runtime, the Event Loop, and Building Real APIs

Part 5 of 14 · ~2 min

Building an HTTP Server

Before Express enters the picture, here's the version of billhook that actually shipped first — Node's built-in http module, nothing else:

const http = require("http");

const server = http.createServer((req, res) => {
  if (req.method === "POST" && req.url === "/webhooks/meridian") {
    let chunks = [];
    req.on("data", (chunk) => chunks.push(chunk)); // the body arrives as a stream of chunks
    req.on("end", () => {
      const raw = Buffer.concat(chunks);
      handleMeridianWebhook(raw, res);
    });
    return;
  }

  res.writeHead(404, { "Content-Type": "text/plain" });
  res.end("Not found");
});

server.listen(3000, () => console.log("billhook listening on 3000"));

Worth noticing: req doesn't arrive with a parsed body sitting on it — it's a readable stream, and accumulating 'data' chunks and waiting for 'end' is the only way to get the whole thing (streams get their own chapter shortly). Routing is a hand-checked if on req.method and req.url. And there's no concept of middleware at all — the one route this version has does its own body-accumulation dance inline, and the moment billhook needs a second route (the internal support API, a few chapters from now), that dance either gets copy-pasted or extracted into something that looks suspiciously like what a framework already gives you for free.

Why frameworks like Express exist

The moment billhook needs more than that one route — real path parameters, size-limited JSON parsing, auth that applies to some endpoints and not others, error responses that look the same everywhere — hand-checking req.method/req.url and manually buffering every request body stops scaling almost immediately. Express solves three specific problems the raw http module leaves entirely to you:

  • Routing — declaring app.post("/webhooks/meridian", handler) directly, with no hand-checking of req.method and req.url anywhere in sight.
  • Middleware — one composable pipeline that anything cross-cutting can plug into once (the same shape shows up well past Node — see Middleware Pipelines Compared) rather than every handler separately remembering to invoke the same logging, auth, and CORS logic on its own.
  • Body parsing — everything the raw version was doing inline by hand — buffering chunks, checking content type — collapses into a single req.body already sitting there by the time your handler runs.

None of this replaces the runtime layer from the previous section, either — Express is http.createServer the whole way down, just with a considerably friendlier API bolted on top of it.