Hono's runtime "framework API" is the WHATWG Fetch standard: Request, Response, Headers, URL, ReadableStream, FormData, and WebCrypto. The package.json description says it plainly -- "Web framework built on Web Standards" -- and the code holds to it: core dispatch (src/hono-base.ts) constructs nothing but standard objects, which is why one app.fetch signature serves Cloudflare Workers, Deno, Bun, Fastly Compute, and (via a translation server) Node.js.
This choice serves the project concretely:
- Portability without abstraction layers. There is no Hono-specific request object to marshal per runtime;
c.req.rawis the platform'sRequest. - Zero dependencies stay zero. Crypto for JWTs and ETags uses
crypto.subtle; body parsing usesrequest.formData(); there is nothing to import (see Zero dependencies by design). - Testing needs no server.
app.request('/hello')builds a standardRequestand callsfetchdirectly -- the same objects in tests and production.
The one non-standard interface is declared, not imported
Workers' ExecutionContext is not a Web Standard, so Hono declares the shape structurally in src/context.ts rather than depending on Workers types:
export interface ExecutionContext {
/**
* Extends the lifetime of the event callback until the promise is settled.
*/
waitUntil(promise: Promise<unknown>): void
/**
* Allows the event to be passed through to subsequent event listeners.
*/
passThroughOnException(): void
// …
}
Any runtime that passes a compatible object gets c.executionCtx; runtimes that do not simply cause an explicit throw when a handler asks for it.
Adapters cover the gaps the standards leave
Three things the Fetch standard does not define: serving files from disk, upgrading to WebSockets, and reading the client's connection info. Those -- and only those -- are what src/adapter/ provides, one directory per runtime, each exporting the same small surface:
export { serveStatic } from './serve-static'
export { toSSG, denoFileSystemModule } from './ssg'
export { upgradeWebSocket } from './websocket'
export { getConnInfo } from './conninfo'
export { serveStatic } from './serve-static-module'
export { upgradeWebSocket } from './websocket'
export { getConnInfo } from './conninfo'
Nine adapter directories exist: aws-lambda, bun, cloudflare-pages, cloudflare-workers, deno, lambda-edge, netlify, service-worker, vercel. The Lambda adapters are the heaviest because AWS does not speak fetch at all -- src/adapter/aws-lambda/handler.ts translates API Gateway v1/v2, ALB, and VPC Lattice event payloads into standard Request objects and back.
The division of labor is strict: adapters translate at the edges; nothing inside src/ core branches on the runtime. When an app needs runtime detection, the hono/adapter helper (src/helper/adapter/) provides env() and getRuntimeKey() -- again as helpers outside the core dispatch path.
Consequences you can rely on
- A handler that only touches
c,Request, andResponseis portable across all supported runtimes by construction; the runtime-tests suites (runtime-tests/*) run near-identical apps on each runtime to enforce this. - Node.js support lives outside this repo (
@hono/node-server, present here only as a devDependency for the Node runtime tests) because Node is the one target that needs a real HTTP-to-fetch translation server.