Back to roadmaps nodejs Course

HTTP Server from Scratch

While frameworks like Express or Fastify are popular, learning how to build a web server using the native http module is essential for understanding server-side Web engineering.


1. Creating a Basic HTTP Server

Node.js provides the native http module. The createServer() method takes a callback function with request (req) and response (res) arguments:

import http from "http";

const server = http.createServer((req, res) => {
  // Set headers and response body
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("Hello World from Node.js Native Server\n");
});

const PORT = 3000;
server.listen(PORT, () => {
  console.log(`Server is running at http://localhost:${PORT}`);
});

2. Inspecting the Request Object

The req parameter is an instance of http.IncomingMessage. It is a readable stream, allowing you to extract:

  • req.url: The path of the request (e.g., /api/users?id=5).
  • req.method: The HTTP verb (e.g., GET, POST, PUT, DELETE).
  • req.headers: A dictionary containing all header keys and values.
import http from "http";

const server = http.createServer((req, res) => {
  console.log(`HTTP Method: ${req.method}`);
  console.log(`Request URL: ${req.url}`);
  console.log(`User-Agent: ${req.headers["user-agent"]}`);

  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ status: "ok", path: req.url }));
});

server.listen(3000);

3. Parsing Request Bodies (POST/PUT data)

Because request data is streamed, we must collect incoming buffers chunk by chunk, concatenate them, and parse the payload when the transmission completes:

import http from "http";

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

    // Accumulate stream chunks
    req.on("data", (chunk) => {
      body += chunk.toString();
    });

    // Parse when stream ends
    req.on("end", () => {
      try {
        const payload = JSON.parse(body);
        console.log("Login payload:", payload);
        
        res.writeHead(200, { "Content-Type": "application/json" });
        res.end(JSON.stringify({ success: true, message: `Welcome, ${payload.username}` }));
      } catch (err) {
        res.writeHead(400, { "Content-Type": "text/plain" });
        res.end("Invalid JSON format");
      }
    });
  } else {
    res.writeHead(404, { "Content-Type": "text/plain" });
    res.end("Endpoint not found");
  }
});

server.listen(3000);

4. Sending Structured HTTP Responses

The res parameter is an instance of http.ServerResponse, which inherits from WritableStream.

  • res.writeHead(statusCode, headers): Writes status code and response headers.
  • res.setHeader(name, value): Sets or updates a single response header.
  • res.write(chunk): Appends content data to the outgoing body stream.
  • res.end(chunk): Flushes all remaining response data and terminates connection.
Published on Last updated: