Building an HTTP Server From Scratch

April 18, 2024

What Is This?

A handcrafted HTTP/1.1 server built without using Node.js's built-in http module. It works directly at the TCP level using the net module, manually implementing all HTTP parsing, routing, middleware, and response formatting. The server ships with a working Todo CRUD API backed by a flat-file database as a demo application.

Zero external dependencies everything is built from first principles.

How It Works

The HTTP Engine

The core server creates a raw TCP server with net.createServer() and exposes an Express-like API — app.route(), app.use(), and app.listen() — all custom-built from scratch.

On each incoming TCP connection, it:

  1. Splits the raw buffer into headers and body at \r\n\r\n
  2. Parses the HTTP request line, headers, route params, and query strings
  3. Runs the middleware chain sequentially with a next() pattern
  4. Matches a route and calls its handler, or returns a 404

Request Parser

Manually parses raw HTTP text extracting method, path, HTTP version, headers, dynamic route parameters (:id patterns), and query strings. Also handles OPTIONS/CORS preflight requests.

Response Builder

Constructs valid HTTP/1.1 responses from scratch — status line, standard headers (Date, Content-Type, Content-Length, Connection), and body — written to the TCP socket with proper \r\n\r\n separation.

Middleware Layer

Three middlewares implemented:

  • Static file server — serves files from a public/ directory with correct MIME types
  • Body parser — handles application/json and multipart/form-data (boundary-based MIME splitting implemented manually following RFC 1341)
  • CORS — sets cross-origin headers and handles preflight

Demo: Todo CRUD API

A fully functional REST API demonstrating all the framework features:

GET    /task          Fetch all tasks
POST   /task          Create a new task
PUT    /task/:id      Update a task by ID
PUT    /task/done/:id  Toggle task completion
DELETE /task/:id      Delete a task by ID
POST   /file          Upload a file via multipart/form-data

Tasks are persisted as concatenated JSON objects in a flat text file — no database needed.

Key Design Decisions

  • No framework, no http module — the entire HTTP lifecycle is built from first principles using only raw TCP sockets
  • Express-like middleware chain — middlewares receive (req, res, next, body) and call next() to pass control, with a headersSent guard to prevent double-writes
  • Dynamic routing — route params like :id are resolved at request time by comparing path segments against registered route patterns
  • Manual multipart parsing — boundary-based MIME splitting without any library
  • MIME type registry — maps content types to their serialization functions for encoding/decoding

What I Learned

Building an HTTP server from the TCP level up revealed exactly what frameworks like Express abstract away — from raw byte buffer handling and header parsing to the middleware chain pattern and content negotiation. Implementing multipart form data parsing manually gave me a deep appreciation for the HTTP specification and how file uploads actually work at the wire level.

View the source code →