cheat sheet
hono
Package-level reference for Hono — minimal routing, typed handlers, middleware, RPC client, and deployment to Cloudflare Workers, Bun, Deno, Node, and Vercel Edge.
hono
What it is
hono is a tiny web framework (Japanese for "flame") built on Web standards (Request, Response, URL). It was designed for edge runtimes — Cloudflare Workers, Deno Deploy, Vercel Edge, Bun, and Lambda — where startup time and bundle size matter. The same code runs unchanged on Node via a built-in adapter.
Reach for Hono when deploying to edge / serverless and you want Express-shaped ergonomics with a Web-standards API surface. Reach for fastify if you need a Node-first server with the deepest ecosystem of plugins; reach for elysia if you are 100% Bun; reach for raw Request / Response handlers if you want zero abstraction.
Install
npm install hono
Output: added hono to dependencies
pnpm add hono
Output: added 1 package, linked from store
yarn add hono
Output: added hono
bun add hono
Output: installed hono
Many people start from a runtime-specific template:
npm create hono@latest my-api
Output: interactive prompts (Cloudflare Workers, Bun, Node, Deno, Vercel, Netlify); scaffolds a minimal API.
Versioning & Node support
The current line is hono@4.x. Hono moves quickly — minor releases ship every few weeks with additive features and occasional breaking changes flagged in the changelog.
- Runs on any Web-standards runtime: Cloudflare Workers, Deno, Bun, Vercel Edge, Netlify Edge, AWS Lambda (with an adapter), Node 18+ (via
@hono/node-server). - Pure ESM. CJS users need a bundler.
- TypeScript types bundled.
- Semver observed; consult the changelog for
4.xminor bumps before upgrading.
Package metadata
- Maintainer: Yusuke Wada + core team
- Project home: github.com/honojs/hono
- Docs: hono.dev
- npm: npmjs.com/package/hono
- License: MIT
- First released: 2022
- Downloads: rapidly growing — millions of weekly downloads as the standard edge framework.
Peer dependencies & extras
hono itself has no peer deps; the runtime-specific extras live in sibling packages.
@hono/node-server— adapter to host Hono on Node's HTTP server@hono/zod-validator,@hono/valibot-validator,@hono/typebox-validator— request validation middleware@hono/swagger-ui,@hono/zod-openapi— OpenAPI integration@hono/jwt,@hono/oidc-auth— auth middleware@hono/sentry— error reportinghono-rate-limiter— rate limiting
Alternatives
| Package | Trade-off |
|---|---|
express | Mature Node ecosystem; CJS-first; not designed for edge runtimes. |
fastify | High-performance Node-first; rich plugin system. Less edge-focused. |
elysia | Bun-native, end-to-end typed; not portable to Workers/Edge. |
koa | Slim Node middleware; predates the Web standards revival. |
itty-router | Minimal Workers-only router. Tinier than Hono but fewer features. |
oak | Deno-flavoured koa-like; Deno-first. |
Native fetch handler | Zero deps; you re-implement routing, parsing, error handling. |
Real-world recipes
Minimal API
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("Hello!"));
app.get("/json", (c) => c.json({ ok: true }));
export default app;
Output: GET / returns Hello!; GET /json returns {"ok":true}; works as-is on Workers, Bun, and Deno.
Typed dynamic route
app.get("/users/:id", (c) => {
const id = c.req.param("id");
return c.json({ id, name: "Alice" });
});
Output: GET /users/42 returns {"id":"42","name":"Alice"}; param is type-inferred.
Logger + CORS middleware
import { Hono } from "hono";
import { logger } from "hono/logger";
import { cors } from "hono/cors";
const app = new Hono();
app.use(logger());
app.use("/api/*", cors({ origin: "https://example.com" }));
app.get("/api/health", (c) => c.json({ ok: true }));
export default app;
Output: each request is logged; /api/* accepts CORS preflight from the configured origin.
Request validation with Zod
import { Hono } from "hono";
import { z } from "zod";
import { zValidator } from "@hono/zod-validator";
const app = new Hono();
const PostSchema = z.object({ title: z.string().min(1) });
app.post("/posts", zValidator("json", PostSchema), (c) => {
const body = c.req.valid("json");
return c.json({ created: body });
});
export default app;
Output: invalid JSON returns 400 with the issue list; valid JSON reaches the handler with body typed as { title: string }.
Streaming response
import { stream } from "hono/streaming";
app.get("/feed", (c) =>
stream(c, async (s) => {
for (let i = 0; i < 5; i++) {
await s.write(`chunk ${i}\n`);
await s.sleep(500);
}
}),
);
Output: five chunks streamed at 500 ms intervals; client sees the body progressively.
Hono RPC client
hc produces a typed client from the server's app type.
// server.ts
import { Hono } from "hono";
const app = new Hono().get("/users/:id", (c) =>
c.json({ id: c.req.param("id"), name: "Alice" }),
);
export type AppType = typeof app;
export default app;
// client.ts
import { hc } from "hono/client";
import type { AppType } from "./server";
const client = hc<AppType>("https://api.example.com");
const res = await client.users[":id"].$get({ param: { id: "42" } });
const data = await res.json();
console.log(data.name);
Output: end-to-end type safety — data.name is string because the server returns that shape.
Cloudflare Workers deployment
// src/index.ts
import { Hono } from "hono";
type Bindings = { DB: D1Database };
const app = new Hono<{ Bindings: Bindings }>();
app.get("/users", async (c) => {
const rows = await c.env.DB.prepare("SELECT * FROM users").all();
return c.json(rows.results);
});
export default app;
# wrangler.toml
name = "my-api"
main = "src/index.ts"
compatibility_date = "2025-10-01"
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "..."
wrangler deploy
Output: deploys to https://my-api.<account>.workers.dev; c.env.DB is the D1 binding.
Production deployment
Hono runs unchanged on every major Web-standards runtime. The deployment harness is what changes.
- Cloudflare Workers / Pages.
wrangler deployafternpm run build. Free tier covers many small APIs; D1, KV, R2, Queues are first-class. - Bun.
bun src/index.ts— Bun calls the default export'sfetchdirectly. - Node. Wrap with
@hono/node-server:
import { serve } from "@hono/node-server";
import app from "./app";
serve({ fetch: app.fetch, port: 3000 });
Output: Node listens on port 3000 and dispatches to Hono.
- Vercel Edge. Export
runtime: 'edge'and the Hono app from the API route. - AWS Lambda. Use
hono/aws-lambdaadapter; deploy via SAM or SST. - Deno.
Deno.serve(app.fetch)runs without an adapter.
Performance tuning
Hono is already among the fastest JavaScript frameworks. The knobs are around routing and middleware ordering.
- Use
RegExpRouter(default in 4.x) for dense route trees.TrieRouteris the fallback when patterns include featuresRegExpRoutercannot express. - Avoid per-request allocations in middleware. Build static objects at module scope.
c.textis faster thanc.jsonfor plain strings — skips aJSON.stringifycall.- Mount middleware narrowly.
app.use('/api/*', heavy)instead ofapp.use('*', heavy)so static asset requests skip it. - Cache headers.
c.header('Cache-Control', 'public, max-age=60')for cacheable responses — at the edge, this is the biggest single performance win. - Async work in the request critical path.
c.executionCtx.waitUntil(promise)(on Workers) returns the response immediately while finishing background work.
Version migration guide
Hono iterates fast. The headline jumps:
| From | To | Key changes |
|---|---|---|
hono@3 | hono@4 | Adapter packages moved out of core (hono/node-server → @hono/node-server). Validator middlewares moved to @hono/*-validator. New app.basePath semantics. |
Before (3.x):
import { Hono } from "hono";
import { zValidator } from "hono/zod-validator";
import { serve } from "hono/node-server";
After (4.x):
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { serve } from "@hono/node-server";
Output: install the moved packages explicitly; behaviour is unchanged.
Within 4.x minors: check the changelog before bumping — minor releases sometimes adjust middleware option shapes.
Security considerations
- CORS defaults are restrictive — verify the origin list.
cors({ origin: "*" })with credentials is dangerous; specify exact origins. csrf()middleware is required for cookie-based auth endpoints; without it, a cross-site form can POST to your API.- Validate every input. Do not trust
c.req.json()until it has been schema-validated;@hono/zod-validator(or valibot) is the standard. - Rate limit.
hono-rate-limiteror KV/Durable Object-backed limiters for Workers. - JWT verification.
@hono/jwtverifies signatures but does not check audience/issuer by default — passaudienceandissuerexplicitly. - HTML escape if rendering HTML. Hono's
c.html()doesn't sanitise — usehono/jsxor escape manually. - Don't leak request bodies in error responses. A validation failure that echoes the body to the client can leak sensitive data.
Testing & CI integration
Unit test with Vitest
// app.test.ts
import { describe, it, expect } from "vitest";
import app from "./app";
describe("API", () => {
it("GET / returns hello", async () => {
const res = await app.request("/");
expect(res.status).toBe(200);
expect(await res.text()).toBe("Hello!");
});
it("POST /posts validates input", async () => {
const res = await app.request("/posts", {
method: "POST",
body: JSON.stringify({}),
headers: { "Content-Type": "application/json" },
});
expect(res.status).toBe(400);
});
});
Output: app.request simulates a fetch against the app in memory — no port binding required.
Type-check the RPC client
tsc --noEmit
Output: any mismatch between client call shape and server route surfaces as a TypeScript error.
Ecosystem integrations
| Package | Role |
|---|---|
@hono/node-server | Node HTTP server adapter |
@hono/zod-validator / @hono/valibot-validator | Schema validation middleware |
@hono/zod-openapi | OpenAPI spec generation |
@hono/swagger-ui | Swagger UI mounted at a route |
@hono/jwt | JWT auth middleware |
@hono/sentry | Sentry error reporting |
hono-rate-limiter | Rate limiting |
drizzle-orm | DB layer; common pairing |
@cloudflare/workers-types | TypeScript types for Workers bindings |
Troubleshooting common errors
TypeError: c.req.valid is not a function — zValidator middleware not mounted on that route; or you forgot to import { zValidator } from @hono/zod-validator (4.x package move).
Cannot find module 'hono/node-server' — Hono 4.x moved it; install @hono/node-server instead.
Error: No matching route at runtime even though the route exists — middleware mounted before the route shortcircuited. Reorder app.use and app.get calls.
Body has already been consumed — you called c.req.json() twice. Cache the parsed body in middleware and read once.
c.env is undefined on Workers — running locally without wrangler dev or with an incorrect binding name. wrangler.toml and the Bindings type must match.
Type 'Response' is not assignable to type 'TypedResponse<...>' — Hono types the response when you use c.json / c.text. Returning a raw Response loses the RPC type. Use the c.* helpers.
Hot reload breaks state — modules cached in dev; restart wrangler dev or bun --hot when changing top-level singletons.
When NOT to use this
- You need the Node-specific plugin ecosystem. Fastify and Express have older, larger plugin catalogues.
- You need Express-compatible middleware. Hono uses its own middleware signature; bring-your-own-Express-middleware doesn't work directly.
- Long-lived stateful connections. WebSocket / SSE is supported, but Node + Fastify (or Bun + Elysia) may be more ergonomic for long-running socket servers.
- You need a runtime-agnostic ORM that wraps Workers and Node identically. Drizzle works on both, but the ecosystem here is younger.
See also
- JavaScript: fastify — Node-first alternative
- Concept: http — request/response semantics, status codes, headers