Skip to content
Back to projects
[completed]6 min read

Reiatsu

Started May 20, 2025·Updated January 10, 2026
TypeScriptNode.jsHTTP(Node.js Core)

Reiatsu is a zero-dependency, type-safe HTTP server framework for Node.js. It is built on Node.js core modules only: http, zlib, crypto. No Express, no Fastify, no Koa. The routing algorithm, middleware pipeline, request/response context, input validation, and security headers are all implemented from scratch on top of Node's native http module.


The Challenge

Node.js's core http module gives you raw request and response event listeners. That is it. Everything else is manual work:

  • Streams: Request bodies arrive as streams. Reading JSON means accumulating buffer chunks, handling encoding, and catching parse exceptions.
  • Routing: No built-in router. Matching dynamic paths like /users/:id/posts/:postId requires a pattern-matching engine.
  • Type safety: Inferring route parameters at compile time from a path string literal so that ctx.params.id is fully type-checked by TypeScript is hard to get right.
  • Middleware: Middlewares need sequential execution, async support, global error handling, and graceful shutdown.

My Role: Sole developer and architect. Framework core, router, middleware composer, npm package, full type safety.

Zero dependencies, zero supply-chain risk. Every feature, from routing to compression to security headers, is built on Node.js core modules. No express, no fastify, no path-to-regexp. No dependency audit headaches.


The Solution

Reiatsu puts routing, middleware, security, and compression into a single package that weighs under 45KB.

Core Features

  • Zero-dependency security: Built on http, zlib, and crypto. No supply-chain vulnerabilities.
  • Dynamic route inference: Compile-time type safety for route parameters. ctx.params.id is checked at build time, not runtime.
  • Async middleware composer: Onion-model pipeline with async/await support and a global error boundary.
  • Built-in utilities: Rate limiter, CORS, CSRF token validation, security headers (HSTS, CSP), HTML escaping, Brotli compression.

Quick Start

import { router, serve } from "reiatsu";
 
// Parameters are automatically inferred and type-checked!
router.get("/users/:userId/posts/:postId", (ctx) => {
  const { userId, postId } = ctx.params;
  ctx.json({ userId, postId });
});
 
serve(3000);

Technical Architecture

The framework intercepts raw Node.js request/response streams, compiles them into a unified context, dispatches through the middleware stack, matches the route, and streams the output:

graph TD %% Styling classDef default fill:#1e1e2e,stroke:#cdd6f4,stroke-width:1px,color:#cdd6f4; classDef highlight fill:#f5c2e7,stroke:#cdd6f4,stroke-width:2px,color:#11111b; classDef pipeline fill:#89b4fa,stroke:#cdd6f4,stroke-width:1px,color:#11111b; Client([Incoming TCP Socket Connection]) --> Server[Node.js http.Server] Server -->|Emits 'request' event req, res| Creator[Reiatsu Context Creator] Creator -->|Wraps req/res into Context| Middleware[Middleware Pipeline <br/> Onion Execution] subgraph Onion["Onion Middleware Stack"] M1[Middleware 1] M2[Middleware 2] Handler[Route Handler] end Middleware -->|1. calls next| M1 M1 -->|2. calls next| M2 M2 -->|3. calls next| Handler Handler -->|4. returns| M2 M2 -->|5. returns| M1 M1 -->|6. resolves| Middleware Middleware --> Matcher[Route Matcher <br/> RegExp Compilation] Matcher --> Formatter[Response Formatter] Formatter -->|Pipes to zlib Gzip/Brotli| Out([Piped to Client Socket Stream]) class M1,M2,Handler pipeline; class Creator,Middleware,Matcher,Formatter default; class Client,Out highlight;

Engineering Deep Dives

1. Type-Safe Route Parameter Inference

Most Node frameworks require manual typing for route parameters. Reiatsu parses route paths at compile time using recursive template literal types:

// Route parameter extraction engine
export type ExtractRouteParams<Path extends string> = Path extends `/${infer P}`
  ? ExtractRouteParams<P>
  : Path extends `${infer P}/`
    ? ExtractRouteParams<P>
    : ExtractParamsFromPathSegments<Path, {}>;
 
type ExtractParamsFromPathSegments<
  PathSegment extends string,
  Acc extends Record<string, string>,
> = PathSegment extends `${infer Segment}/${infer Rest}`
  ? ExtractParamsFromPathSegments<
      Rest,
      Acc & ExtractParamOrWildcardFromSegment<Segment>
    >
  : Acc & ExtractParamOrWildcardFromSegment<PathSegment>;
 
type ExtractParamOrWildcardFromSegment<Segment extends string> =
  Segment extends `*`
    ? { wildcard: string }
    : Segment extends `:${infer ParamName extends string}(${infer _Regex})`
      ? { [K in ParamName]: string }
      : Segment extends `:${infer ParamName extends string}`
        ? { [K in ParamName]: string }
        : {};

When you write /posts/:postId/comments/:commentId or /user/:id(\\d+), the compiler dissects the string literal, identifies dynamic segments (including regex constraints and wildcards), and compiles them into a type-checked shape.

Compile-time safety, runtime performance. The route parameter types resolve entirely at build time. Zero runtime overhead. The type inference happens in the TypeScript compiler, not in your application code.

router.get("/posts/:postId/comments/:commentId", (ctx) => {
  const { postId, commentId } = ctx.params; // Fully autocompleted & type-checked!
});

2. Asynchronous Middleware Onion Composer

Each middleware receives the request Context and a next callback, letting it run logic before and after subsequent layers:

export function compose(...middlewares: Middleware[]): Middleware {
  if (middlewares.length === 0) {
    return async (ctx, next) => await next();
  }
 
  if (middlewares.length === 1) {
    return middlewares[0];
  }
 
  return async (ctx, next) => {
    let index = -1;
 
    const dispatch = async (i: number): Promise<void> => {
      if (i <= index) {
        throw new Error("next() called multiple times");
      }
 
      index = i;
      const fn = i === middlewares.length ? next : middlewares[i];
 
      if (!fn) return;
 
      await fn(ctx, () => dispatch(i + 1));
    };
 
    await dispatch(0);
  };
}

Empty and single-middleware lists get fast-paths. The global errorHandlerMiddleware catches exceptions thrown deep inside handlers by awaiting the next() call.

3. Stream-Based Response Processing & Brotli Compression

Loading large files into memory before sending them creates bottlenecks. Reiatsu uses Node.js streams to keep memory constant regardless of response size:

// Memory-efficient response streaming inside Context
streamFile(
  filePath: string,
  options?: {
    contentType?: string;
    disposition?: "inline" | "attachment";
  }
) {
  const stream = createReadStream(filePath);
  const contentType = options?.contentType || getMimeType(filePath);
 
  this.stream(stream, {
    ...options,
    contentType,
    filename: basename(filePath),
  });
}

File read streams pipe directly into the raw HTTP socket (this.res). Data chunks go to the network as they are read from disk. JSON and text responses are buffered first for error safety, then compressed on-the-fly with Brotli or Gzip.

Stream vs. buffer: the trade-off. Streaming is memory-efficient but once headers are sent, the HTTP status code cannot change. JSON responses buffer first to allow error handling. Large files stream directly; mid-stream failures close the socket.

The compression middleware intercepts res.write and res.end, captures chunks, and pipes them through a compressor:

// Excerpt from src/middleware/compression.ts
export const createCompressionMiddleware = (
  options: CompressionOptions = {},
): Middleware => {
  return async (ctx, next) => {
    const acceptEncoding = ctx.req.headers["accept-encoding"] || "";
    const originalWrite = ctx.res.write.bind(ctx.res);
    const originalEnd = ctx.res.end.bind(ctx.res);
    let chunks: Buffer[] = [];
 
    // Intercept write calls to buffer response data
    ctx.res.write = (chunk: any) => {
      if (chunk)
        chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
      return true;
    };
 
    ctx.res.end = (chunk?: any) => {
      if (chunk)
        chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
      const buffer = Buffer.concat(chunks);
 
      // Compress using Brotli if supported by browser/client
      if (preferBrotli && acceptEncoding.includes("br")) {
        ctx.res.setHeader("Content-Encoding", "br");
        ctx.res.removeHeader("Content-Length");
        const br = createBrotliCompress();
 
        ctx.res.write = originalWrite;
        ctx.res.end = originalEnd;
        br.pipe(ctx.res);
        br.end(buffer);
      } else {
        /* Fallback to gzip or raw transmission */
        ctx.res.write = originalWrite;
        ctx.res.end = originalEnd;
        originalEnd(buffer);
      }
    };
    await next();
  };
};

Technical Challenges & Trade-offs

1. Zero-Dependency Footprint vs. Development Time

No third-party libraries meant writing custom parsers for query strings, URL parameter regex extraction, cookie serialization, and HTML escaping.

  • Decision: Custom parsers increased initial effort, but the result is a lightweight package with no dependency audit issues and high runtime performance from single-purpose design.

2. Regex-Based Route Parsing vs. Radix Trees

Radix tree routing is efficient for heavy route structures, but dynamic parameters and wildcards require complex state management.

  • Decision: Reiatsu compiles route path strings into named-capture-group regular expressions at startup. For applications under 100 routes, regex execution is fast, easy to maintain, and supports complex matching rules.

3. Error Boundary Safety vs. Stream Interruption

In Node.js HTTP, once headers or body chunks are sent, the status code cannot change. A mid-stream exception means no 500 Internal Server Error page.

  • Decision: A double-phase response buffer. JSON responses accumulate and send atomically, letting middleware intercept errors. Large files bypass the buffer and stream directly. Mid-stream failures close the socket cleanly.

Results & Impact

Under 45KB, fully type-safe. Reiatsu is a complete HTTP framework in under 45KB, compared to Express's megabyte-scale footprint. Every route parameter is type-checked at compile time.

  • Zero-dependency security: 100% dependency-free NPM package. No supply-chain vectors.
  • Compile-time safety: Route parameter validation prevents runtime undefined crashes.
  • Lightweight footprint: Under 45KB, suitable for edge deployments.
  • Published to npm: Complete TypeScript declarations, built-in CORS, rate limiting.