honojs/hono
github.com/honojs/hono ↗ · 561 files · 3.9 MB
Hono - means flame🔥 in Japanese - is a small, simple, and ultrafast web framework built on Web Standards.
Overview
Hono is a TypeScript web framework whose application boundary is a standard Request-to-Response fetch handler. It serves developers building APIs and web applications across JavaScript runtimes, with interchangeable routers, composable middleware, typed clients, JSX rendering, streaming, and static generation. At commit f23b146afcec63606144cde50b5fbd360dd60238, the repository also maintains independently packaged runtime adapters alongside deprecated in-package adapters.
System map
The public entrypoint exports Hono and supporting types, while HonoBase owns registration and dispatch. Request and Context wrap standard web objects, and runtime adapters translate platform boundaries. Storage behavior inspected here belongs to optional cache and SSG facilities. Distribution includes npm ESM/CJS/declarations, JSR source exports, and separate adapter packages.
- src/index.ts exports Hono, Context, HonoRequest types, handler/environment types, and typed client inference utilities.
- The default Hono extends HonoBase and uses SmartRouter with RegExpRouter followed by TrieRouter unless options.router is supplied.
- HonoBase supplies dynamically initialized HTTP method registration, on(), use(), route metadata, and the fetch application boundary.
- Context owns bindings, request-local variable storage, response state and execution context; it lazily constructs HonoRequest.
- The root package distributes dist with ESM, CommonJS and declaration entrypoints; Vite emits these as three unbundled outputs.
- The pnpm workspace contains the root and adapters packages; @hono/aws-lambda and @hono/bun expose ESM dist entrypoints and require hono >=4.13.9 as a peer.
- Optional persistence uses the runtime Cache API for responses and an injected FileSystemModule for generated files.
- Root scripts define type-check-plus-Vitest testing, runtime-specific tests, adapter tests, and Vite-plus packaging with publint.
- Release automation distinguishes v* root tags from @hono/* adapter tags, stages npm publication with provenance, and separately publishes JSR packages.
Feature catalog
The user-facing product is a framework API and exported modules rather than a fixed application route set. Routing, responses, middleware and validation form the server surface. Typed clients, JSX, streaming, WebSockets and static generation extend it into full web application development. The export-map inventory is broader than the implementation paths examined in detail.
- Applications register get/post/put/delete/options/patch/query/all handlers, arbitrary method/path combinations through on(), and path-scoped middleware through use().
- route() groups Hono applications, basePath() prefixes registration, and mount() integrates another Request handler with configurable request replacement and arguments.
- Context exposes text, JSON, HTML, redirect and custom-body response construction; onError() and notFound() customize application failures.
- validator() supports JSON, form, query, path parameter, header and cookie targets, allowing transformed data or an early Response.
- hc() supplies proxy-generated HTTP calls, $url/$path helpers and $ws connections, with form, JSON, query, parameter, header and cookie inputs.
- The JSR export map exposes authentication, IP restriction, body limits, cache, compression, CORS, CSRF, ETag and trailing-slash middleware.
- Additional exported middleware covers timeouts, timing, logging, method handling, request IDs, language selection, secure headers, JSON formatting and middleware combinations.
- Hono JSX exports components, context, hooks, Suspense and ErrorBoundary; the DOM package additionally exports render, portals, flushSync and form-state hooks.
- stream() returns a streaming Response; streamSSE() adds SSE headers and writeSSE() formats data, event, id and retry fields.
- toSSG() generates route output files with configurable concurrency, MIME extensions and plugins; redirectPlugin() converts redirects into HTML pages.
- Bun and Deno adapters export static serving, SSG, WebSocket and connection helpers; Cloudflare Workers exports static serving, WebSocket upgrade and connection information.
- Lambda offers buffered handle() and streamHandle(); Vercel forwards Requests directly, Netlify passes its context in bindings, and Service Worker handling can fall back to network fetch on 404.
Data flow
Incoming Requests pass through path extraction and router matching into a per-request Context. Middleware and validators progressively attach state before response construction. Clients and adapters serialize or translate data at the outer boundaries. Cache writes and SSG files are separate optional output paths with different ownership and lifetime.
- fetch(request, env, executionCtx) forwards the method and supplied runtime values into #dispatch(), which extracts the path, matches routes and constructs Context.
- Router results carry handler tuples and either parameter indexes with a stash or direct parameter maps; HonoRequest resolves the active route's parameters and decodes their values.
- compose dispatch assigns req.routeIndex before each handler, calls the next handler recursively and places returned responses into Context.
- HonoRequest caches consumed body representations and rebuilds alternate representations through Response while retaining the original media type except for FormData.
- Validation parses the selected boundary, awaits validationFunc(), returns Response results immediately, and stores other results through addValidatedData() for valid().
- Context JSON responses stringify objects and supply application/json; #newResponse combines existing/prepared headers, explicit headers and status.
- ClientRequestImpl serializes forms or JSON, substitutes path parameters, appends query parameters and invokes supplied fetch or global fetch; GET and HEAD omit request bodies.
- Lambda EventProcessor builds an HTTPS Request from event path/domain/query/headers, decodes base64 bodies, then serializes the Response to text or base64 with platform cookie handling.
- Cache keys encode the logical key, method and configured header variants; QUERY additionally hashes representation metadata and body bytes within a configured size ceiling.
- SSG routes become application Responses, then text or ArrayBuffer content, MIME-derived file paths and filesystem writes after output-containment validation.
Behavior traces
Source reads followed registration and first-request routing, validated requests, cache persistence, Lambda translation and static generation. These traces describe function order and branches rather than executed observations. The default router becomes fixed after its first match. SSG executes application handlers as part of both route discovery and output production.
- Startup registration: new Hono() calls HonoBase's constructor, initializes registration functions, selects SmartRouter, and app.get()/use() call #addRoute() to append metadata and router entries.
- First request: fetch() → #dispatch() → getPath() → SmartRouter.match(); SmartRouter replays stored routes into candidates, skips UnsupportedPathError, and replaces match with the selected router's bound implementation.
- Single-handler GET: #dispatch() invokes the handler directly; a returned Response is delivered and Promise rejection reaches #handleError(). For a Promise resolving without a response, a finalized Context returns c.res; otherwise notFound is used. A synchronous nullish return falls back to notFound.
- Validated POST: compose.dispatch() invokes validator('json') → req.json() → cached text → JSON.parse() → validationFunc(); transformed output enters addValidatedData(), next() reaches the handler, and c.json() serializes its response.
- Malformed JSON: validator catches body parsing failure and throws HTTPException(400); compose catches Error instances and passes them to onError, while the default handler uses getResponse().
- Cache hit: cache middleware checks method and Authorization, computes a QUERY digest when needed, generates the key, opens caches, matches it and returns a new Response without calling downstream handlers.
- Cache miss and persistence: cache.match() misses → await next() → status/Vary/Cache-Control/Set-Cookie checks → response.clone() → awaited cache.put() or executionCtx.waitUntil(cache.put()).
- Lambda request: handle(app) chooses getProcessor(), calls createRequest(), passes event/requestContext/lambdaContext to app.fetch(), then calls createResult(); request-construction TypeErrors become 400 results.
- Static generation: toSSG() combines hooks → fetchRoutesContent() filters routes and calls app.fetch() for metadata → parameter expansion calls app.request() → response hooks transform/filter content → saveContentToFile() creates directories and writes files.
- SSE output: streamSSE() constructs TransformStream/SSEStreamingApi, sets protocol headers and starts run(); callback writeSSE() escapes line structure and writes framed events, and run() closes the stream in finally.
Testing signals
Tests and CI were inspected, not executed; no passing result or measured coverage is asserted. The bounded inspection found Vitest unit/type tests, JSDOM rendering tests, runtime projects and adapter suites. CI schedules build, checks and several runtime jobs, but configured jobs are not execution evidence. Mocked platform tests and coverage exclusions limit what these sources establish.
- The root test script first runs tsc against tsconfig.spec.json, then vp test --run; the spec configuration enables Vitest globals and Hono JSX.
- Vitest includes colocated src tests and build tests in main, imports runtime project configurations, and excludes standalone *.case.test.* files from main discovery.
- Coverage uses V8 with JSON/text/HTML reports and excludes runtime-tests, benchmarks, perf-measures and specified compile-time type files.
- Hono tests explicitly separate requests without middleware from composed requests and inspect status, body, response headers, bindings and 404 behavior.
- compose tests exercise request-local state, post-next response-header mutation, returned JSON and handlers returning Promises from non-async functions.
- Type tests use expectTypeOf and Equal/Expect assertions for bindings, variables, validated inputs and inferred route schema; validator tests assert query acceptance and rejection.
- DOM tests import JSDOM and test element creation, children, keys and refs; two Vitest projects run DOM-related suites with different JSX import sources.
- Cache unit tests replace global caches with a Map-backed MockCache, so the inspected assertions establish intended key/hit behavior rather than real provider persistence.
- SSG path tests assert lexical containment across traversal, partial-prefix names, current directories, parent directories, Windows drives and UNC roots.
- Lambda backpressure tests stub awslambda, stream into a delayed Node Writable, assert bounded read-ahead and completion, and exercise mid-stream failure reporting.
- Published declaration tests create a temporary consumer project, symlink Hono, invoke tsc through spawnSync and clean up; they test packaging behavior beyond source-only type assertions.
- Main CI configures check/build/test, Deno JSX/runtime testing, Bun including Windows, Fastly, workerd and a Node version matrix; a later setup-node step also appears in the matrix job and merits checking before assuming its actual runtime version.
Risk map
These are source observations and explicitly labeled hypotheses, not confirmed vulnerabilities. Authentication, outbound fetch, file access, body consumption and response caching are sensitive boundaries. Shared routing state and duplicated adapter generations increase compatibility concerns. Partial test inspection cannot establish that any critical flow is wholly untested.
- Routing registration becomes immutable after SmartRouter's first match, and basePath clones share its router and route list; adding routes after serving begins can throw across related instances.
- Single-handler dispatch bypasses compose, whereas multi-handler dispatch enforces Context finalization; changes to return/error semantics must account for both paths.
- bodyLimit trusts Content-Length when Transfer-Encoding is absent; otherwise it consumes and buffers chunks before invoking downstream handlers.
- Hypothesis: callers constructing Requests with inconsistent Content-Length could defeat an actual-byte limit on the trusted-length branch; whether a deployment permits this depends on its ingress runtime. (inferred)
- CSRF protection applies to unsafe methods with form-compatible media types and allows the request if either Origin or Sec-Fetch-Site validation succeeds; it is not a universal check on every request type.
- JWT middleware requires a configured algorithm and secret, accepts bearer headers or configured cookies, and places verified claims in jwtPayload; JWKS verification rejects symmetric algorithms and checks the allowlist and matching key algorithm.
- JWKS verification performs fetch(options.jwks_uri, init), and JWK middleware can derive that URI from Context; outbound destination trust and fetch policy therefore belong to application configuration.
- Cache bypasses Authorization requests and responses with Set-Cookie, private/no-cache/no-store or unsupported Vary; its default background write accesses executionCtx, which throws if absent.
- Static serving rejects routed percent signs by default and suspicious decoded path segments, but configured path and rewriteRequestPath feed runtime file access; these callbacks/options are sensitive trust boundaries.
- SSG enforces lexical output containment before filesystem writes; the inspected filesystem interface has no realpath or symlink-resolution operation, so physical containment is not established by this check.
- SSG invokes application handlers for metadata and generated content, while createdDirs is module-global; generation lifecycle and handler side effects need attention when reusing an application or changing output directories.
- proxy() forwards through supplied fetch or global fetch and can carry raw request credentials; strictConnectionProcessing optionally removes headers named by an inbound Connection header and validates their syntax.
Extension points
Hono extends chiefly through handlers, middleware, injectable routers and explicit exported modules. Runtime bindings and adapters keep platform concerns outside core dispatch. Factory and validator APIs preserve types while allowing application-specific behavior. SSG has an explicit ordered plugin interface, and build plugins enforce distribution conventions.
- Custom routers implement Router<T> with name, add() and match(), then enter the default Hono through options.router.
- Custom getPath(request, {env}) can alter routing boundaries; strict mode controls selection of normal versus non-strict path extraction.
- Handler and MiddlewareHandler define Context/Next extension contracts; use() registers middleware with the ALL method.
- Factory.createApp() merges default app options and invokes initApp; createMiddleware() preserves middleware typing and createHandlers() collects defined handlers.
- validator() accepts an application validation function rather than prescribing a schema library, with runtime transformation and early-response behavior.
- mount() supports custom optionHandler and replaceRequest functions to bridge other frameworks and runtime argument conventions.
- The static-serving base accepts getContent, join and isDir implementations plus path rewriting, MIME overrides and found/not-found hooks.
- SSGPlugin exposes beforeRequestHook, afterResponseHook and afterGenerateHook, with single or multiple functions; FileSystemModule abstracts mkdir and writeFile.
- SSG plugin order is observable: redirectPlugin must precede defaultPlugin because the latter filters non-200 responses.
- hc() accepts a custom query builder, per-request/base options and injected WebSocket factory; proxy() separately supports customFetch.
- New adapters are workspace-discovered, separately versioned packages; maintainer instructions require corresponding deprecated src/adapter exports.
- Packaging plugins validate npm/JSR export-map parity and transform emitted declarations, including removal of private fields and appendExportEmptyToDts.
Contributor handoff
Tests and CI were inspected, not executed, and this brief is a bounded inventory rather than exhaustive coverage. Begin edits from the relevant runtime path and its colocated tests, preserving public typing and packaging contracts. Adapter changes require attention to both separate packages and deprecated compatibility surfaces. The original task authorizes read/search only; the commands described here are references for a later authorized implementation task.
- Read HonoBase, compose, Context and HonoRequest together for dispatch changes because they jointly own handler order, route indexes, response finalization and parsed input.
- Preserve both single-handler and composed dispatch semantics and HEAD-to-GET behavior; the Hono test suite distinguishes composed and uncomposed requests.
- Use the declared pnpm@12.6.0 package manager and existing scripts as the command reference; root test includes type checking, while runtime and adapter scripts have distinct scopes.
- For a later authorized verification, inspect the smallest colocated suite first, then use root test and the affected runtime/adapter script; declaration tests additionally exercise a temporary external consumer and rely on dist/types behavior. (inferred)
- Keep new public exports synchronized between package.json and jsr.json because the build plugin checks parity in both directions.
- Adapter work should begin with docs/MAINTAINING.md and the relevant package manifest/config; adapter versions and peer compatibility are independent of root Hono releases.
- Do not consume req.raw bodies casually when later middleware or forwarding needs reconstruction: cloneRawRequest throws when the body was consumed without a HonoRequest cache.
- Read SSG plugin order, route discovery and output containment before changing generation; custom plugins replace the default plugin list, and discovery invokes handlers.
- Follow the established formatter settings: two spaces, no semicolons, single quotes, LF and print width 100; lint requires top-level type import conventions.
- Treat observed assertions and CI steps as verification plans, not passing evidence: cache tests use mocked caches, Lambda tests use mocked awslambda, and CI definitions only specify what automation should run.
- Avoid release/build cleanup scripts during source-only investigation: root build invokes remove-dist, and maintainer adapter release instructions describe committing, tagging and pushing.
Languages
| Language | Files | Share of source |
|---|---|---|
| TypeScript | 421 | 99% |
| JSON | 48 | docs/config |
| Markdown | 18 | docs/config |
| YAML | 15 | docs/config |
| HTML | 4 | docs/config |
| JavaScript | 2 | 0% |
| Shell | 1 | 0% |
| TOML | 1 | 0% |
Start reading here
Probable source entrypoint: src/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
src/ (312 files)
Source layout — src/
| Path | Files |
|---|---|
src/middleware/ | 58 |
Repository structure
| Top-level path | Files |
|---|---|
src/ | 312 |
adapters/ | 111 |
benchmarks/ | 67 |
runtime-tests/ | 20 |
.github/ | 12 |
perf-measures/ | 12 |
docs/ | 8 |
Docs found: README.mddocs/CODE_OF_CONDUCT.mddocs/CONTRIBUTING.mddocs/MAINTAINING.mddocs/MIGRATION.md
Package
package.json — hono v4.13.12, 22 declared dependencies.
@hono/node-server@types/jsdom@types/node@types/ws@typescript/native-preview@vitest/coverage-v8bun-typeseditorconfig-checkeresbuildjsdommswnp
CI & config entrypoints
README.md— Repository readmesrc/index.ts— TS entrypointadapters/deno/vite.config.ts— Vite configadapters/lambda-edge/vite.config.ts— Vite configadapters/vercel/vite.config.ts— Vite configadapters/aws-lambda/vite.config.ts— Vite configadapters/bun/vite.config.ts— Vite configadapters/netlify/vite.config.ts— Vite configadapters/cloudflare-workers/vite.config.ts— Vite configadapters/service-worker/vite.config.ts— Vite config
Structural health signals
Average score 8.3/10 across 96 analyzed source files; 7 hotspots surfaced.
src/hono-base.ts— 547 lines, score 5.2 (Large file, Deep nesting)src/jsx/dom/render.ts— 879 lines, score 5.5 (Large file, Deep nesting)src/types.ts— 2525 lines, score 5.7 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
f23b146afcec2026-10-01 — fix(jsx): allow JSXNode function component results (#5476)6d73a74f97572026-10-01 — docs(request): fix jsdoc comments for some getters (#5445)deff529656cf2026-10-01 — test(client): simulate network error for undefined route in parseResponse test (#5439)6abd35b0a5f32026-09-30 — 4.13.1295eb860473d22026-09-30 — chore(deps): upgrade vite-plus to 1.0.0 (#5464)