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.raw is the platform's Request.
  • Zero dependencies stay zero. Crypto for JWTs and ETags uses crypto.subtle; body parsing uses request.formData(); there is nothing to import (see Zero dependencies by design).
  • Testing needs no server. app.request('/hello') builds a standard Request and calls fetch directly -- 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:

src/context.ts
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:

src/adapter/deno/index.ts
export { serveStatic } from './serve-static'
export { toSSG, denoFileSystemModule } from './ssg'
export { upgradeWebSocket } from './websocket'
export { getConnInfo } from './conninfo'
src/adapter/cloudflare-workers/index.ts
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, and Response is 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.

Sources: src/context.ts, src/adapter/*/index.ts · last synced 2026-08-10 · 26de731 · version 4.13.1