H3 started in late 2020, during the rise of edge workers. With H3 + unjs/unenv, we could run Nitro deployments in worker environments with Node.js compatibility, best of both worlds! Since v1.8, H3 has improved its support for web standards.
But still, H3 was primarily based on Node.js APIs with a compatibility layer for web standards. Logical choice at the time, given Node.js's popularity amongst JavaScript server runtimes.
Thanks to evolving web standards by initiatives like WinterTC and runtime support in Deno, Bun, and the latest Node.js, ecosystem is ready to embrace web standards first class for server development. Benefits include:
- Cross-runtime interoperability (Node.js, Deno, Bun, Workers, etc.)
- Cross-framework compatibility (H3, Hono, Elysia, etc.)
- Cross-environment compatibility (shared and familiar code between frontend and backend)
- Leverage more of runtime native primitives like (Request, URL, Headers, etc.)
- Easier API testing
💥 Srvx: Universal Web Server API
A major challenge was that Node.js lacks built-in support for web-standard HTTP servers. For node:http
compatibility, an adapter is needed to bridge Node.js IncomingMessage
to web Request
, and to handle web Response
via Node.js ServerResponse
. We have implemented a compatibility layer that bridges interfaces and achieves up to 96.98% of native node:http
performance (see benchmarks).
Runtimes such as Deno, Bun, and Edge Workers pioneered the adoption of web standards for servers, but they did not agree on the same interface due to lack of enough specs. So how do you access the client IP address and additional context? How do you set the server port and TLS options? How do you handle WebSocket upgrades? Each runtime created its own API.
We have created 💥 srvx: A unified layer that works everywhere exactly the same. Compatible with Deno, Bun, Node.js, Service Workers, Edge Workers.
Example
// Dynamic adapter will be used based export conditions of each runtime
import { serve } from "srvx";
serve({
port: 3000,
// tls: { cert: "server.crt", key: "server.key" }
fetch(req) {
// Server Extensions: req.ip, req.waitUntil(), req.runtime?.{bun,deno,node,cloudflare,...}
return new Response("👋 Hello there!");
},
});
⚡ H3: Tiny Server Composer 🎶
We worked hard to minimize and simplify H3’s scope.
- 🪶 Optimized for performances, lighter than a feather.
- 👌 Intuitive typed handlers, responses and errors.
- 🧩 Reusable middleware and plugins.
- 🌳 Fast routing.
- ➕ Built-in utilities.
- ❤️ Maximum compatibility based on web standards.
import { H3, serve } from "h3";
const app = new H3().get("/", () => "⚡️ Tadaa!");
serve(app, { port: 3000 });
🪶 Lighter Than a Feather
We approached benchmarking with a new method that focuses on measuring the overhead introduced by the framework itself, rather than the network layer. Our goal is to optimize all relevant measurements together, making the numbers as close as possible to a baseline where no framework is added or used. This method allowed H3 to achieve optimized latency improvements per request and a dramatically smaller core bundle size.
Measurement | H3 v1 | 🚀 H3 v2 |
---|---|---|
Request Handling | Node: 36 µs Bun: 27 µs Deno: 7 ms | Node: 7 µs (5x faster) Bun: 3 µs (9x faster) Deno: 1.2 µs (156x faster) |
Bundle Size | min: 101 kB min+gzip: 39.6 kB | min: 9,1 kB (91% smaller) min+gzip: 3.6 kB (90% smaller) min: 5.2 kB / min+gzip: 2.1 kB (fetchable handlers) |
fetch
handler with new URL(req.url).pathname
for routing. In other words, you get the benefits of H3 with nearly zero performance cost!✅ Typed Web Standards
H3 adopts web standard APIs such as Request, Response, URL, and Headers, without introducing new conventions on top of the standards.
We have launched a new initiative to strongly type Web APIs: ✅ fetchdts. Integrated into the H3, now we combine the best of both worlds—standards and the convenience of types.
import { defineHandler } from "h3";
const handler = defineHandler(async (event) => {
// URL Parsing
const { pathname, searchParams } = event.url;
// Access to request headers (try auto-completion in editor!)
const accept = event.req.headers.get("Accept");
// Read body
const bodyStream = await event.req.body;
const bodyText = await event.req.text();
const bodyJSON = await event.req.json();
const bodyFormData = await event.req.formData();
// Access to runtime specific context
const { deno, bun, node } = event.req.runtime;
// Prepare response (h3 does this smartly)
event.res.headers.set("Content-Type", "application/json");
return { hello: "web" };
});
Now go ahead and call handler .fetch
:
const response = await handler.fetch("/");
// 🧙 Typed response: { hello: string; }
const json = await response.json();
🧩 Middleware and Plugins
H3 now offers an ergonomic, composable way to chain middleware using next()
function (inspired by Hono middleware 💛).
Additionally, we have introduced a simple yet powerful pattern to extend H3 apps using reusable plugins.
import { H3 } from "h3";
const app = new H3().use(async (event, next) => {
// ... before response ...
const body = await next();
// ... after response ...
event.res.headers.append("x-middleware", "works");
event.waitUntil(sendMetrics(event));
return body;
});
import { defineHandler, basicAuth } from "h3";
export default defineHandler({
middleware: [basicAuth({ password: "test" })],
handler: (event) => `Hello ${event.context.basicAuth?.username}!`,
});
import { H3, onRequest } from "h3";
const app = new H3().use(
onRequest((event) => {
console.log(`Request: [${event.req.method}] ${event.url.pathname}`);
}),
);
import { H3, onResponse } from "h3";
const app = new H3().use(
onResponse((response, event) => {
console.log(`Response: [${event.req.method}] ${event.url.pathname}`, body);
}),
);
import { H3, onError } from "h3";
const app = new H3().use(
onError((error, event) => {
console.error(
`[${event.req.method}] ${event.url.pathname} !! ${error.message}`,
);
}),
);
import { H3, serve, definePlugin } from "h3";
const logger = definePlugin((h3, _options) => {
if (h3.config.debug) {
h3.use((req) => {
console.log(`[${req.method}] ${req.url}`);
});
}
});
const app = new H3({ debug: true })
.register(logger())
.all("/**", () => "Hello!");
next
callback is optional. Middleware can be written like v1 without returning a response.⬆️ Migration from Version 1
We've tried to minimize breaking changes. Most of utilities preserved backward compatibility.
🙌 Unified H(TTP) Server Tools for Everyone
H3 and related projects moved to a dedicated github org and new h3.dev domain (thanks to the donation from syntax.fm and other sponsors 💛).
Under the H3 umbrella, we maintain several key components for universal JavaScript servers.
All fully open and usable with or without H3, and with any JavaScript runtime.
- ⚡️ h3: Minimal HTTP Framework.
- 🌳 rou3: Lightweight JavaScript Router.
- 💥 srvx: Universal Web-based Server API.
- 🔌 crossws: Cross-platform WebSocket support.
❤️ Special Thanks
This release would not have been possible without wonderful contributors, feedback from the community, inspirations from web-standard frameworks including Hono and Elysia, and sponsors who made it possible to work on open source.
🗺️ Roadmap to v2 (stable)
Next steps:
- Gather feedback from community.
- Finalize API updates based on feedbacks.
- Ensure ecosystem compatibility and upgrade for Nitro v3.