Open-source project
hattipjs/hattip avatar
hattipjs/hattip

Hattip: Express style handlers that run on Node, Deno and the edge

Like Express, but for the future

1,383 stars19 forksTypeScriptMIT

At a glance

What is it?
Hattip is a set of JavaScript packages for writing HTTP servers as a single function that takes a request context and returns a Response, with adapters for Node, Deno, Bun, Cloudflare Workers, Fastly, Netlify and Vercel. The whole core API is a few functions, and the bet is that writing to the Fetch API and WinterCG now saves a rewrite later.
Who is it for?
Adopt Hattip if you are starting a new HTTP service and want to be able to move it between a Node server, an edge runtime and Deno without rewriting the handler, and if you are comfortable with the Fetch API as your programming model.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • hattipjs/hattip1,383 ★ · +2
Star history
Repository2026-10-202026-11-07
hattipjs/hattip1.4k1.4k

2 Stars in the last 18 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The entire core API is a handler and a compose function

The README's most striking line is that this is the entirety of the Hattip API: everything else is middleware functions that add features and development tools. The core is a handler, a function that receives a context and returns a Response:

js
export default (context) => {
  const { pathname } = new URL(context.request.url);
  if (pathname === "/") {
    return new Response("Hello from Hattip.");
  }
  if (pathname === "/about") {
    return new Response(
      "This HTTP handler works in Node.js, Cloudflare Workers, and Fastly.",
    );
  }
  return new Response("Not found.", { status: 404 });
};

There is no request object with a dozen properties, and no response builder. A Hattip handler is passed a context object that represents the request context and contains `context.request`, which is a standard Request, and it returns a standard Response or a promise of one, both following the Fetch API standard. That is why the README says that if you are familiar with service workers, Cloudflare Workers or Deno you will feel at home, and if not, you should learn the standard. The stated reason for the whole design is portability: instead of writing server code that only works with Express.js, you write server code that can be deployed to AWS, Cloudflare Workers, Fastly, Vercel, a VPS and more. The project follows WinterCG, which it describes as laying the foundation beyond the Fetch API, and it states the ambition plainly, an ecosystem of universal middlewares usable across the entire JavaScript universe.

Middleware is a sequence with an explicit next

The middleware system comes from the `compose` function in the `@hattip/compose` package, and it composes multiple handlers into a single one. The semantics are simple and worth reading carefully, because the return value is what decides the flow. Each handler is called in sequence until one returns a response. A handler passes control to the next one either by not returning anything or by calling `ctx.next()`. The second form is the interesting one, because `ctx.next()` returns the response that the downstream handlers produced, so a middleware can modify it before returning it:

js
import { compose } from "@hattip/compose";

// Example of making things available in `ctx`
// Middleware to parse the URL into a URL object
const urlParser = (ctx) => {
  ctx.url = new URL(ctx.request.url);
};

// Example of modifying the response
// Middleware to add an X-Powered-By header
const poweredBy = async (ctx) => {
  const response = await ctx.next();
  response.headers.set("X-Powered-By", "Hattip");
  return response;
};

Two extension points complete the contract. A handler can return or throw a Response, or anything with a `toResponse` method, which is how non web standard return values are adapted. And handlers can set `context.handleError` to deal with uncaught errors, which is the error path that a framework normally owns. The README also notes, with a self aware aside, that Hattip does have a router, and that the compose example is there to illustrate the basics. That framing is the honest one: the core is a mechanism, and a router is one middleware built on it.

Adapters are the portability story, and the matrix is explicit

The adapter list is the most informative table in the README, because it shows exactly where the abstraction has been made to work. Bun, Cloudflare Workers, Deno including Deno Deploy, Express.js, Fastly, Lagon, Netlify Edge Functions, Netlify Functions, Node.js, uWebSockets.js, Vercel Edge and Vercel Serverless are all supported, and Service Workers are marked as still in progress. Each adapter is its own package under `@hattip/`, and the shape of the code differs by how the platform expects an entry point. On Node you create a server and listen on a port, importing `createServer` from `@hattip/adapter-node` and passing the handler:

js
// entry-node.js
import { createServer } from "@hattip/adapter-node";
import handler from "./handler.js";

createServer(handler).listen(3000, "localhost", () => {
  console.log("Server listening on http://localhost:3000");
});

On Cloudflare Workers the same handler is wrapped and exported as a fetch function, using the default export of the adapter package:

js
// entry-cfw.js
import cloudflareWorkersAdapter from "@hattip/adapter-cloudflare-workers";
import handler from "./handler.js";

export default {
  fetch: cloudflareWorkersAdapter(handler),
};

The Express case is the pragmatic one, and the README frames it as the escape hatch for the one Express library that has no replacement anywhere else. The same Node adapter package also exports `createMiddleware`, so a Hattip application can be mounted inside an existing Express app alongside other middleware, which is how a team can adopt the pattern without rewriting a whole server.

A type only core, and a monorepo that builds for two ecosystems

The package layout explains the design. The `core` package is a type only package that defines the interface between your application and the platform adapters, so the contract is expressed in TypeScript types and the runtime behaviour lives in the adapters. Around it sit the adapters, one package per platform, and bundlers for worker and serverless platforms, so that a build step can produce a bundle in the shape a given platform expects. The repository itself is a pnpm workspace with a `packages/` directory, and the workspace root package.json shows how the pieces are built and tested: `dev` and `build` run the packages in parallel with pnpm filters, `test` runs the scripts matching the `cq` or `ci` names, `cq` in turn runs every `test:` script, and `ci` sets `CI=true` and streams the results per package. The individual test scripts cover the packages, the formatting, and the rest, with prettier checking the tree and vitest as the test runner. There is also a `deno-build/` directory with a `deno.lock`, so the same sources are built for Deno, a `testbed/` directory that holds runnable setups for individual platforms, and `examples/` with a `basic/` and a `vite/` project.

The bet, and where it could still cost you

The case for Hattip is a bet on standards, and the README states it without pretending it is risk free. It is built on current and future web standards, the Fetch API and WinterCG, and the project says it believes in a diverse but interoperable future for the JavaScript ecosystem. The benefit is concrete: a handler written once runs on a Node server today and on an edge runtime later, with no rewrite, which is the same promise that the Fetch API itself made for service workers. The risk is that the platforms are not identical, and the adapter list is where the differences are absorbed. The Node adapter gives you a server and a port, the Cloudflare adapter gives you a fetch export, the Express adapter gives you a middleware, and the Service Workers entry is still marked as work in progress, which tells you the matrix is not finished. There is also the ecosystem question the project names itself after: the universal middleware ecosystem is an aspiration, and until it exists, the middleware you will actually use are the ones the project and its community have written, which is a much smaller set than the Express ecosystem you might be leaving.

Editorial conclusion

Adopt Hattip if you are starting a new HTTP service and want to be able to move it between a Node server, an edge runtime and Deno without rewriting the handler, and if you are comfortable with the Fetch API as your programming model. Do not adopt it as a drop in replacement for a mature Express application, because the README is explicit that the API surface is deliberately tiny and everything else is middleware, and because the Express route is a middleware bridge rather than a first class target. Before you commit, check the adapter list for the platform you intend to run on, since Service Workers are still marked as work in progress, and read the `core` package, which is type only and defines the contract between your application and the adapters. The licence is MIT, the repository publishes no GitHub releases, and the last push was on 2026-10-14.

Frequently asked questions

What is the Hattip handler signature?

A handler is a function that receives a context object and returns a standard Response or a promise of one. The context contains `context.request`, which is a standard Request object, and both follow the Fetch API standard.

Which platforms have Hattip adapters?

Bun, Cloudflare Workers, Deno including Deno Deploy, Express.js, Fastly, Lagon, Netlify Edge Functions, Netlify Functions, Node.js, uWebSockets.js, Vercel Edge and Vercel Serverless. Service Workers are listed as still in progress.

How does the Hattip middleware system work?

The `compose` function from the `@hattip/compose` package composes multiple handlers into one. Each handler is called in sequence until one returns a response, and a handler passes control on either by returning nothing or by calling `ctx.next()`, which returns the downstream response so it can be modified.

Can I use Hattip inside an existing Express application?

Yes. The Node adapter package also exports `createMiddleware`, so a Hattip application can be mounted in an Express app as middleware, which the README frames as the way to keep the one Express library that has no replacement.

Official sources

  1. hattipjs/hattip on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/hattipjs-hattip.svg)](https://hysenlabs.com/projects/hattipjs-hattip)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/hattipjs-hattip.svg)](https://hysenlabs.com/projects/hattipjs-hattip)

Was this useful?