Skip to content

Repository files navigation

This project is for educational purposes only. It is not intended for production use. It demonstrates how to build a stream-based, on-demand HTTP framework from scratch using Node.js internals.

fasthttp

A lightweight, stream-based HTTP framework for Node.js built from scratch in TypeScript.

Design principles:

  1. Stream based — built directly on Node.js http module, no abstraction layers
  2. On demand — objects are created and recycled via pooling, not pre-allocated
  3. ESM — native ES modules throughout, no CommonJS

How It Works

Object Pooling

Every incoming request creates three objects: FastHttpContext, FastHttpRequest, and FastHttpResponse. Instead of allocating and discarding them per request, fasthttp reuses them through ObjectPool. When a response finishes, the pool calls a CLASS_CLEAR_SYMBOL method on each object to reset its state, then pushes it back for the next request. This reduces garbage collection pressure under high concurrency.

Static & Dynamic Route Dispatch

The router separates routes into two categories at registration time:

  • Static routes — stored in a Map<method, Map<path, handlers>>. Lookup is O(1).
  • Dynamic routes (with :param or * segments) — stored as arrays and scanned linearly per request.

At dispatch time, the RouterDispatcher first tries the static map. If no match, it falls through to dynamic routes and then to sub-routers. This gives fast static matching while still supporting flexible parameterized paths.

Middleware Chains

Handlers are executed sequentially via a runner function. Each handler receives (context, next). Calling next() advances to the next handler. If a handler throws or returns a rejected promise, the error is caught and emitted on the context's event emitter.

Sub-Router Composition

Routers can be nested using .router(subRouter). Each sub-router has its own base path and middleware stack. The dispatcher consumes path segments as it descends into sub-routers, so /users/42 dispatched through a /users sub-router will match /:id.

Cache-Based Memoization

Expensive computations (URL parsing, header extraction, client IP detection) are wrapped in a Cache class that memoizes results per request. The cache is a simple Map<string, unknown> that gets cleared when the object is recycled.

Event System

Each context has an EventEmitter for lifecycle events: request, response, before-response, after-response, error, and router-level events (router:middlewares, router:handler, router:router). This lets you hook into the request lifecycle without modifying handlers.

Getting Started

Install

npm install fasthttp

Minimal Server

import { FastHttpApplication, FastHttpRouter } from "fasthttp";

const root = new FastHttpRouter().get((context) => {
  context.response.text("Hello World");
});

new FastHttpApplication()
  .createServer({ root })
  .listen(3000, () => console.log("Listening on :3000"));

Usage

Routing

import { FastHttpApplication, FastHttpRouter } from "fasthttp";

const items = [];
let nextId = 1;

const router = new FastHttpRouter()
  .get("/items", (context) => {
    context.response.json(items);
  })
  .get("/items/:id", (context) => {
    const id = Number(context.request.parameters.id);
    const item = items.find((i) => i.id === id);
    if (!item) return context.response.status(404).text("Not found");
    context.response.json(item);
  })
  .post("/items", async (context) => {
    const body = await context.request.json();
    const item = { id: nextId++, ...body };
    items.push(item);
    context.response.status(201).json(item);
  })
  .put("/items/:id", async (context) => {
    const id = Number(context.request.parameters.id);
    const index = items.findIndex((i) => i.id === id);
    if (index === -1) return context.response.status(404).text("Not found");
    const body = await context.request.json();
    items[index] = { ...items[index], ...body };
    context.response.json(items[index]);
  })
  .delete("/items/:id", (context) => {
    const id = Number(context.request.parameters.id);
    const index = items.findIndex((i) => i.id === id);
    if (index === -1) return context.response.status(404).text("Not found");
    items.splice(index, 1);
    context.response.text("");
  });

new FastHttpApplication()
  .createServer({ root: router })
  .listen(3000, () => console.log("Listening on :3000"));

Middleware

import { FastHttpApplication, FastHttpRouter } from "fasthttp";

const app = new FastHttpApplication({ timeoutMs: 5000 });

app.createServer({
  root: new FastHttpRouter()
    .use((context, next) => {
      console.log(`${context.request.raw.method} ${context.request.raw.url}`);
      next();
    })
    .get("/", (context) => {
      context.response.text("Hello");
    })
    .get("/slow", async (context) => {
      await new Promise((r) => setTimeout(r, 200));
      context.response.text("Slow response");
    }),
  onRequest: (ctx) => {
    ctx.event.once("error", (_err, _req, res) => {
      res.status(500).text("Server error");
    });
  },
}).listen(3000);

Sub-Routers

import { FastHttpApplication, FastHttpRouter } from "fasthttp";

const users = new FastHttpRouter("/users")
  .get("/:id", (context) => {
    context.response.json({ userId: Number(context.request.parameters.id) });
  });

const posts = new FastHttpRouter("/posts")
  .get("/:id", (context) => {
    context.response.json({ postId: Number(context.request.parameters.id) });
  });

new FastHttpApplication()
  .createServer({
    root: new FastHttpRouter()
      .router(users)
      .router(posts),
  })
  .listen(3000, () => console.log("Listening on :3000"));

GET /users/42 returns { "userId": 42 }. GET /posts/7 returns { "postId": 7 }.

JSON Body Handling

import { FastHttpApplication, FastHttpRouter } from "fasthttp";

new FastHttpApplication()
  .createServer({
    root: new FastHttpRouter()
      .post("/echo", async (context) => {
        context.response.json(await context.request.json());
      })
      .post("/uppercase", async (context) => {
        const text = await context.request.text();
        context.response.text(text.toUpperCase());
      }),
  })
  .listen(3000);

Request/Response Measuring

import { FastHttpApplication, FastHttpRouter } from "fasthttp";

const root = new FastHttpRouter().get((context) => {
  context.response.text("Hello World");
});

const application = new FastHttpApplication();
const server = application.createServer({
  root,
  onRequest: (context) => {
    const start = performance.now();
    context.event.once("response", () => {
      const end = performance.now();
      console.log(`Request ${context.request.id}: ${(end - start).toFixed(2)}ms`);
    });
  },
});

server.listen(3000);

API

FastHttpApplication

Option Type Default Description
timeoutMs number Request timeout passed to http.createServer
timerWheelResolution number 100 Timer wheel tick interval in ms
pool.maxObjectSize number 100 Max pooled objects per pool
const app = new FastHttpApplication({ timeoutMs: 30000 });
const server = app.createServer({ root, onRequest });

FastHttpRouter

Method Description
.get(path, ...handlers) Register GET handler
.post(path, ...handlers) Register POST handler
.put(path, ...handlers) Register PUT handler
.patch(path, ...handlers) Register PATCH handler
.delete(path, ...handlers) Register DELETE handler
.use(...handlers) Register middleware
.router(subRouter) Mount a sub-router

Routes support static paths (/items) and dynamic segments (/items/:id).

FastHttpRequest

Access via context.request (or context.req).

Property/Method Description
context.request.raw Raw Node.js IncomingMessage
context.request.parameters Route params (:id -> { id: "42" })
context.request.headers Request headers
context.request.id Request ID (from x-request-id header or auto-generated UUID)
context.request.ip Client IP address
context.request.url Parsed URL object
context.request.text() Read body as string
context.request.json() Read body as parsed JSON
context.request.buffer() Read body as Buffer
context.request.stream() Raw readable stream
context.request.extras Arbitrary key-value store per request

FastHttpResponse

Access via context.response (or context.res).

Method Description
context.response.status(code) Set status code (chainable)
context.response.text(payload, contentType?) Send plain text (text/plain)
context.response.json(payload) Send JSON (application/json)
context.response.buffer(payload, contentType?) Send buffer (application/octet-stream)
context.response.stream(contentType?) Get raw writable stream
context.response.locals Arbitrary key-value store per response
context.response.extras Arbitrary key-value store per response

FastHttpContext

Property/Method Description
ctx.request FastHttpRequest
ctx.response FastHttpResponse
ctx.method Lowercase HTTP method
ctx.url Parsed URL
ctx.paths URL path segments
ctx.event EventEmitter for lifecycle events
ctx.abortController AbortController for this request
ctx.running Whether a handler has been dispatched

Events

Event Arguments Description
request (request, response) Fired when request starts
response (request, response) Fired after response finishes
before-response (payload) Fired before response is sent
after-response Fired after response is sent
error (error, request, response) Fired on unhandled errors

Errors

  • FastHttpRequestError — thrown by request header parsing methods
  • FastHttpResponseError — response-level errors

Benchmarks

Results from autocannon (100 concurrent connections, 10 seconds per scenario) on 2026-07-09.

Scenario fasthttp express fastify
JSON 13,250 req/s 15,998 req/s 26,301 req/s
Plaintext 14,542 req/s 16,298 req/s 26,218 req/s
Params 17,480 req/s 14,441 req/s 25,734 req/s
Echo (small) 13,578 req/s 10,917 req/s 15,706 req/s
Echo (large) 11,055 req/s 4,381 req/s 4,536 req/s

Latency (p99)

Scenario fasthttp express fastify
JSON 18ms 10ms 5ms
Plaintext 17ms 9ms 5ms
Params 15ms 9ms 4ms
Echo (small) 18ms 12ms 14ms
Echo (large) 16ms 32ms 31ms

Key takeaways:

  • Fasthttp outperforms Express on parameterized routes (+21%) and large echo payloads (+152%)
  • Fasthttp has lower p99 latency than Express on echo-large (16ms vs 32ms)
  • Fastify leads across the board due to its optimized serialization layer and schema-based routing
  • These benchmarks are educational — the frameworks have different feature sets and optimization targets

Running Benchmarks

npm run bench          # standard benchmark
npm run bench:stress   # stress test

Project Structure

fasthttp/
├── src/
│   ├── application.ts       # FastHttpApplication — server creation, pool management
│   ├── router.ts            # FastHttpRouter — route registration, static/dynamic storage
│   ├── router-dispatcher.ts # RouterDispatcher — route matching and handler execution
│   ├── context.ts           # FastHttpContext — per-request context, events, abort control
│   ├── request.ts           # FastHttpRequest — request wrapper, header/body parsing
│   ├── response.ts          # FastHttpResponse — response wrapper, text/json/buffer/stream
│   ├── cache.ts             # Cache — lazy memoization for computed values
│   ├── errors.ts            # FastHttpRequestError, FastHttpResponseError
│   ├── constant.ts          # Shared symbols and constants
│   ├── types.ts             # TypeScript type definitions
│   └── pools/
│       ├── objects.ts       # ObjectPool — generic object recycling
│       └── timer-wheel.ts   # TimerWheel — efficient timeout management
├── example/
│   ├── simple.js            # Minimal hello world
│   ├── routing.js           # CRUD routes with parameters
│   ├── middleware.js         # Middleware and error handling
│   ├── sub-router.js        # Sub-router composition
│   ├── json-body.js         # JSON/text body handling
│   └── performance.js       # Request timing
├── benchmark/
│   ├── run.js               # Benchmark runner (fasthttp vs express vs fastify)
│   ├── stress.js            # Stress test
│   ├── servers/             # Benchmark server implementations
│   └── history/             # Saved benchmark results
└── test/
    ├── e2e/                 # End-to-end tests
    └── helpers/             # Test utilities

License

ISC

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages