Skip to main content

Command Palette

Search for a command to run...

HTTP & Building a Server From Scratch (Using Core http)

Published
3 min readView as Markdown

if you really want to understand how servers work, you need to go one level lower. You need to touch the raw metal. You need to build a server using the core http module. That’s what this post is about.

In this we are gonna -

  • Understand what HTTP actually is

  • Build a server from scratch

  • Parse routes manually

  • Read headers

  • Handle request bodies using streams

  • Send proper status codes

what is HTTP ?

Starting with the most important/basic thing, what http is actually ? HTTP is just a conversation protocol.

Every HTTP request has:

  • Method (GET, POST, PUT, etc etc)

  • URL

  • Headers

  • Body (optional)

Every HTTP response has:

  • Status code (200, 404, 500…)

  • Headers

  • Body

Creating the smallest possible HTTP server

starting with most basic one

const http = require("http");

const server = http.createServer((req, res) => {
  res.end("Hello World");
});

server.listen(3000, () => {
  console.log("Server running on port 3000");
});

Understanding req and res

req (Incoming Message)

And it mostly contains -

  • req.method

  • req.url

  • req.headers

  • request body as a stream

res (Server response)

It is Used to -

  • set status codes

  • set headers

  • send response body

Routing in nodejs

const server = http.createServer((req, res) => {
  if (req.method === "GET" && req.url === "/") {
    res.end("Home page");
    return;
  }

  if (req.method === "GET" && req.url === "/health") {
    res.end("OK");
    return;
  }

  res.statusCode = 404;
  res.end("Not Found");
});

Routing is just if-else on method and path(i love this analogy, got it from a cohort and still carry with me). Every framework eventually does this just in a cleaner way.

Reading request headers

Headers are metadata.

Auth tokens, content type, user agent all comes through headers.

const server = http.createServer((req, res) => {
  console.log(req.headers);

  res.end("Check your terminal");
});
/* output you'll see in terminal. it might differ a bit
content-type
authorization
user-agent
content-length
*/

Handling request body

Request body is a stream. The body does not come all at once. It arrives in chunks.

const server = http.createServer((req, res) => {
  let body = "";

  req.on("data", (chunk) => {
    body += chunk;
  });

  req.on("end", () => {
    console.log(body);
    res.end("Body received");
  });
});

This pattern matters a lot in real systems. Because -

  • Requests can be huge

  • Streaming avoids loading everything in memory

  • This scales better

Parsing JSON request body

req.on("end", () => {
  try {
    const data = JSON.parse(body);
    console.log(data);
    res.end("JSON parsed");
  } catch (err) {
    res.statusCode = 400;
    res.end("Invalid JSON");
  }
});

On eimportant thing to notice here is that you are responsible for validations here, frameworks just hide these from us.

Sending proper status codes

res.statusCode = 201;
res.end("User created");

Some common status codes with meaning

  • 200 – OK

  • 201 – Created

  • 400 – Bad request

  • 401 – Unauthorized

  • 404 – Not found

  • 500 – Server error

Setting response headers

Headers go out before body

res.setHeader("Content-Type", "application/json");
res.statusCode = 200;
res.end(JSON.stringify({ message: "Hello" }));

Order doesn’t matter in code that much. Node sends them correctly under the hood.

A tiny but complete server

Putting everything together

const http = require("http");

const server = http.createServer((req, res) => {
  if (req.method === "POST" && req.url === "/echo") {
    let body = "";

    req.on("data", (chunk) => {
      body += chunk;
    });

    req.on("end", () => {
      res.setHeader("Content-Type", "application/json");
      res.statusCode = 200;
      res.end(body);
    });

    return;
  }

  res.statusCode = 404;
  res.end("Route not found");
});

server.listen(3000);

Once we understand these foundations of a server Frameworks stop feeling magical. Debugging becomes easier. Performance issues make sense.