tinyhttp
A tiny, ESM-only web framework that's an Express-compatible drop-in, with precompiled routing and a fraction of the dependencies.
Repository Health
Technical Analysis
@tinyhttp/app is the core package of tinyhttp, a modern rewrite of Express for the ESM era. It exports the App class — the same request handler, res.send/res.json, and middleware-chaining API that Express popularized — but compiled to native ES modules with no CommonJS interop, no polyfills, and support only for recent Node.js versions. Route matching runs on regexparam patterns that are precompiled once at registration time rather than re-parsed on every request, and it depends on a handful of workspace-scoped @tinyhttp/* packages instead of Express’s larger dependency tree.
The rest of the tinyhttp monorepo — request/response extensions, cookies, content negotiation, rate limiting, proxy address resolution, and more — ships as separate, independently versioned packages that @tinyhttp/app composes together through its extendMiddleware step, so consumers can swap out or omit pieces they don’t need. It’s aimed at teams who like Express’s API and mental model but want a smaller, all-ESM, actively maintained alternative for new services.
What You Get
- App class - an Express-style application object with
.use(),.get()/.post()/etc., sub-app mounting via.route(), and a.listen()that wrapsnode:http’screateServer. - Extended req/res objects -
req.query,req.params,req.accepts*,res.send,res.json,res.cookie,res.render, and more, attached lazily viaextendMiddleware. - Built-in view rendering - a
Viewclass supporting pluggable template engines throughapp.engine()andres.render(). - Custom error and 404 handling - configurable
onErrorandnoMatchHandlerhooks passed into theAppconstructor. - Sub-application mounting - nest one
Appinside another with.use(path, subApp)for modular route composition.
Common Use Cases
- Migrating off Express - teams wanting Express’s familiar API without its CommonJS/legacy-polyfill baggage.
- Small ESM-only APIs and microservices - projects that want a minimal HTTP framework with few dependencies.
- Custom middleware pipelines - apps that chain multiple
.use()middleware functions for auth, logging, or request transformation. - Server-rendered pages with template engines - apps using
res.render()with engines like eta or Pug viaapp.engine().
Under The Hood
Architecture
App extends Router (from @tinyhttp/router) and acts as the monorepo’s composition root: it wires route matching (regexparam patterns precompiled once at .use()/route-registration time inside pushMiddleware), request extensions (@tinyhttp/req), response extensions (@tinyhttp/res), and template rendering (the View class) into a single class whose .attach handler is bound directly to node’s http.createServer. Middleware resolution runs through a private #find method plus a closure-based handle()/loop() pair that mimics Express’s next()-driven chain but tracks an index instead of slicing arrays per request. extendMiddleware runs first in the chain and lazily attaches every Express-compatible req/res helper (res.send, req.accepts, etc.), instantiating an Accepts negotiator only when an accept-* method is actually called. Sub-apps are supported by nesting App instances into a this.apps map with parent/child links, enabling .route()-based mountpath composition. Because App is the framework’s sole public entrypoint, a change to its constructor or its Router base class would ripple through every consuming @tinyhttp/* package and every app built on top of it.
Tech Stack
Written in TypeScript, compiled via tsc to ESM-only output (type: module, exports: ./dist/index.js), targeting recent Node.js versions. Outside the tinyhttp monorepo itself, its only runtime dependency is regexparam (pinned through the pnpm workspace catalog: mechanism), keeping the install footprint minimal per the project’s own “fewer dependencies than Express” claim. The monorepo uses pnpm workspaces (packages/*) with shared catalog version pinning, native node:http for the HTTP server in .listen() with no framework-level server abstraction, and Changesets-driven, provenance-signed npm releases.
Code Quality
Tests run under Vitest with coverage uploaded to Coveralls in CI; the app package alone carries an extensive test suite covering routing, middleware chaining, error handlers, sub-app mounting, and a regression test tied to a specific security advisory guarding against a handler throwing after headers are already sent. Linting and formatting are unified through Biome rather than separate ESLint/Prettier tooling, enforced in CI across multiple operating systems and Node versions on every push and pull request, with Husky and commitlint gating commit messages. Error propagation follows a typed NextFunction(err) continuation matching Express’s callback-based error-handling convention, and type-unsafe spots are marked with explicit, justified @ts-expect-error comments rather than silent any casts.
What Makes It Unique
tinyhttp’s core differentiator is staying Express-API-compatible (the same res.send, req.params, and (req, res, next) middleware signature) while being fully ESM-native with no legacy CommonJS or polyfill baggage, and precompiling route regex at registration time instead of Express’s per-request path matching — a concrete performance-oriented rewrite of Express’s request-handling loop, reinforced by reusing a shared singleton middleware object for HEAD requests instead of allocating one per request. Splitting nearly every Express built-in (cookies, proxy address resolution, content negotiation, sending files, rate limiting, and more) into independently versioned @tinyhttp/* packages is itself an architectural bet: a pick-only-what-you-need alternative to Express’s more monolithic dependency tree.