npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

superexpressjs

v1.1.0

Published

A minimal Express-style web framework built from scratch on raw TCP sockets — routing, middlewares, error handling and mountable routers with zero dependencies

Readme

SuperExpress.js

A minimal Express-style web framework for Node.js, built from scratch on raw TCP sockets (node:net) — no http module, zero dependencies. Built as a learning project to understand what frameworks like Express actually do under the hood: HTTP parsing, routing, middleware chains, and error handling.

import { superExpress } from "superexpressjs";

const app = superExpress();

app.get("/users/:id", (req, res) => {
  res.json({ id: req.params.id });
});

app.listen(3000);

Install

npm install superexpressjs

Requires Node.js >= 18. Ships as ESM with full TypeScript types.


Table of Contents


Features

  • HTTP from scratch — raw request parsing and response writing over node:net TCP sockets; the http module is never imported
  • Routingget, post, put, patch, delete with route params (/users/:id) and query string parsing (?page=1&limit=10)
  • Three middleware levels — app-level (app.use), router-level (router.use), and route-level (app.get(path, mw1, mw2, handler))
  • Async-safe error handling — errors thrown from handlers and middlewares, sync or async, are caught — no unhandled promise rejections
  • Error middlewares — Express-style (error, req, res, next) chain via app.useError, with a default 500 fallback
  • Mountable routers — group related routes in a Router() and mount them under a path prefix
  • Built-in middleware factoriescors, json, logger, rateLimit, serveStatic (with directory-traversal protection)
  • TypeScript-first — strict types for Request, Response, Handler, Middleware, ErrorMiddleware
  • Zero runtime dependencies

Architecture

                        ┌────────────────────────────────────────────┐
                        │              superExpress()                │
                        │                                            │
 TCP connection         │  ┌──────────────┐    ┌──────────────────┐  │
──────────────────────▶ │  │ net.Server   │───▶│  requestParser   │  │
 raw bytes              │  │ (socket)     │    │  raw text → req  │  │
                        │  └──────────────┘    └────────┬─────────┘  │
                        │                               │            │
                        │                               ▼            │
                        │  ┌──────────────────────────────────────┐  │
                        │  │        app-level middlewares         │  │
                        │  │   cors → rateLimit → serveStatic …   │  │
                        │  └────────────────┬─────────────────────┘  │
                        │                   │ next()                 │
                        │                   ▼                        │
                        │  ┌──────────────┐    ┌──────────────────┐  │
                        │  │  findRoute   │───▶│ route middlewares│  │
                        │  │ exact → :param│   │  then handler    │  │
                        │  └──────────────┘    └────────┬─────────┘  │
                        │                               │            │
                        │            any error, sync or async        │
                        │                               ▼            │
                        │  ┌──────────────────────────────────────┐  │
                        │  │          error middlewares           │  │
                        │  │  (error, req, res, next) → … → 500   │  │
                        │  └──────────────────────────────────────┘  │
                        └────────────────────────────────────────────┘

Source layout:

src/
├── index.ts            # public API barrel (superExpress, Router, middlewares, types)
├── server.ts           # superExpress(): TCP server, dispatch loop, app.use overloads
├── types.ts            # Request, Response, Handler, Middleware, ErrorMiddleware, Route
├── parser/
│   └── requestParser.ts    # raw HTTP text → Request (method, path, headers, query, body)
├── router/
│   ├── router.ts           # Router(): mountable route group factory
│   ├── findRoute.ts        # route table lookup (exact match first, then :param routes)
│   └── matchRoute.ts       # segment-by-segment :param matching → params object
├── middleware/
│   ├── middlewareRegistery.ts  # app-level middleware + error-middleware storage
│   └── factory/                # built-ins: cors, json, logger, rateLimit, staticServer
├── constants/
│   └── http.ts             # status code → reason phrase map
└── utils/
    ├── handleError.ts      # default error handler (log + 500)
    ├── joinPath.ts         # mount-prefix + route-path joining
    └── getMimeType.ts      # file extension → Content-Type

How It Works

1. The TCP server

app.listen(port) creates a net.Server. Each connection's data event delivers the raw HTTP request as plain text — there is no http.IncomingMessage; everything above the socket is implemented in this library:

GET /users/42?verbose=1 HTTP/1.1\r\n
host: localhost:3000\r\n
\r\n

2. Request parsing

parseReq (src/parser/requestParser.ts) turns that text into a Request object:

  1. Split head from body at the first \r\n\r\n
  2. Parse the request line → method, path, version
  3. Split path on ? → path name + query string; decode key=value&… pairs into req.query
  4. Parse each header line on the first : → lowercase keys in req.headers

A malformed request short-circuits to 400 Bad Request. req.params starts empty and is filled in by the router.

3. The response object

A fresh res is built per request, writing HTTP directly to the socket:

  • res.status(code) / res.setHeader(key, value) — chainable setters
  • res.send(body) — writes the status line (HTTP/1.1 200 OK), headers, blank line, and body, then closes the socket
  • res.json(data) — sets Content-Type: application/json and sends JSON.stringify(data)

4. Routing

Every route registration (app.get(path, ...handlers)) pushes a Route record — the last handler is the route handler, anything before it becomes route-level middleware:

{ method: "GET", path: "/users/:id", middlewares: [...], handler }

Lookup (findRoute) is two-phase:

  1. Exact match on method + path — fast path
  2. Param match — for routes containing :, matchRoute compares segment-by-segment; literal segments must be equal, :param segments capture into req.params

/users/42 against /users/:id{ id: "42" }. No match → 404.

5. The middleware chain

Dispatch is a pair of recursive next() chains:

runMiddleware(0)          // app-level: cors → rateLimit → static → …
   └─ next() → runMiddleware(1) → … (exhausted)
        └─ runRouteMiddleware(0)   // router-level + route-level middlewares
             └─ next() → … (exhausted)
                  └─ route handler

Each middleware decides whether to call next(). Not calling it ends the chain — e.g. rateLimit responds 429 directly, serveStatic sends the file and never calls next().

6. Async-safe error handling

The classic Express 4 footgun: an async handler that throws doesn't throw into the framework — the call returns instantly with a rejected promise, the stack is gone, and a plain try/catch sees nothing. The result is an unhandled rejection and a connection that hangs.

SuperExpress handles both failure modes at every call site (handlers, middlewares, error middlewares):

try {
  const result = handler(req, res, next);
  Promise.resolve(result).catch((error) => runErrorMiddleware(error, 0));
} catch (error) {
  runErrorMiddleware(error, 0); // sync throws
}

Promise.resolve(result) is the trick: a sync handler returns undefined (resolves immediately, .catch never fires); an async handler returns a promise whose rejection is now observed. The framework never needs to await anything — handlers drive the response themselves via res.send(); the return value is only watched for failure. This is essentially what Express 5 added.

Caught errors run the error middleware chain (app.useError): each error middleware can respond or call next() to pass the error on. If the chain is exhausted (or an error middleware itself throws), the default handler logs and sends 500 Internal Server Error.

7. Routers and mounting

Router() is a self-contained route group: its own routes array, verbs, and router-level middlewares. Mounting flattens it into the app's route table:

app.use("/users", userRouter);
// for each router route:
//   path        → joinPath("/users", route.path)      "/" → "/users", "/:id" → "/users/:id"
//   middlewares → [...router.middlewares, ...route.middlewares]

Because mounted routes land in the same route table, they flow through the exact same dispatch and error handling as app routes — router support required zero changes to the dispatch loop. The trade-off: mounting copies a snapshot, so register routes on the router before app.use(prefix, router).


Examples

Hello world

import { superExpress } from "superexpressjs";

const app = superExpress();

app.get("/", (req, res) => {
  res.send("<h1>Hello from raw TCP!</h1>");
});

app.listen(3000);

Route params and query strings

// GET /users/42?verbose=1
app.get("/users/:id", (req, res) => {
  res.json({
    id: req.params.id,        // "42"
    verbose: req.query.verbose, // "1"
  });
});

JSON body parsing

import { json } from "superexpressjs";

app.post("/users", json(), (req, res) => {
  // req.body is parsed when Content-Type: application/json
  res.status(201).json({ created: req.body });
});

App-level middlewares

import { cors, logger, rateLimit, serveStatic } from "superexpressjs";

app.use(logger());                                   // method + path per request
app.use(cors({ origins: ["http://localhost:5173"] }));
app.use(rateLimit({ limit: 200, windowMs: 60_000 })); // 429 over the limit
app.use(serveStatic("./public"));                     // files, with traversal protection

Route-level middlewares

const requireAuth = (req, res, next) => {
  if (req.headers["authorization"] !== "secret") {
    return res.status(401).send("Unauthorized");
  }
  next();
};

app.get("/admin", requireAuth, (req, res) => {
  res.send("admin area");
});

Routers

import { superExpress, Router, logger } from "superexpressjs";

const app = superExpress();
const userRouter = Router();

userRouter.use(logger());                      // scoped to this router only
userRouter.get("/", (req, res) => res.send("all users"));
userRouter.get("/:id", (req, res) => res.json({ id: req.params.id }));
userRouter.post("/", (req, res) => res.status(201).send("created"));

app.use("/users", userRouter);
// GET /users · GET /users/:id · POST /users

Error handling

// throw from anywhere — sync or async — and it's caught
app.get("/boom", async () => {
  throw new Error("Async Boom"); // no unhandled rejection
});

// error middlewares run in registration order
app.useError((error, req, res, next) => {
  if (error instanceof Error && error.message === "teapot") {
    return res.status(418).send("I'm a teapot");
  }
  next(); // not mine — pass it on
});

app.useError((error, req, res, next) => {
  res.status(500).json({
    error: error instanceof Error ? error.message : "Internal Server Error",
  });
});

If no error middleware responds, the default handler logs the error and sends 500.

Full application

See examples/basic.ts for a complete app combining CORS, rate limiting, static files, a mounted router, and error middlewares. Run it from a clone of this repo with:

npm run dev

API Reference

superExpress()

Creates an app.

| Method | Description | |---|---| | app.get/post/put/patch/delete(path, ...handlers) | Register a route; last handler is the route handler, the rest are route-level middlewares | | app.use(middleware) | Register an app-level middleware | | app.use(prefix, router) | Mount a Router() under a path prefix | | app.useError(errorMiddleware) | Register an (error, req, res, next) error middleware | | app.listen(port) | Start the TCP server |

Router()

Creates a mountable route group with the same verb methods plus router.use(middleware) for router-level middlewares.

Request

| Property | Type | Description | |---|---|---| | method | string | HTTP method | | path | string | Path without query string | | params | Record<string, string> | Route params from :segments | | query | Record<string, string> | Decoded query string pairs | | headers | Record<string, string> | Lowercased header names | | body | string | Raw body (object after json() middleware) |

Response

| Method | Description | |---|---| | res.status(code) | Set status code (chainable) | | res.setHeader(key, value) | Set a header (chainable) | | res.send(body) | Write the response and close the connection | | res.json(data) | Send JSON with the right Content-Type |

Built-in middleware factories

| Factory | Description | |---|---| | cors({ origins }) | Sets Access-Control-Allow-Origin for allowed origins | | json() | Parses application/json bodies into req.body; 400 on invalid JSON | | logger() | Logs timestamp -> [METHOD] /path per request | | rateLimit({ limit, windowMs }) | Fixed-window rate limiting; 429 over the limit | | serveStatic(rootDir) | Serves files with MIME detection (.html, .css, .js, .json, .png, .jpg) and path-traversal protection |


Known Limitations

This is a learning project, not a production framework:

  • Assumes one complete HTTP request per TCP data event — no Content-Length buffering or chunked transfer encoding, so large request bodies may be truncated
  • No keep-alive or pipelining — every response closes the connection
  • Routes must be registered on a router before mounting it (mounting copies a snapshot)
  • Rate limiting is in-memory, per-process, and global (not per-IP)

License

ISC