What Is Hono?
Hono (Japanese for "flame") is a web framework that runs on any JavaScript runtime. It was built from the ground up for the Edge, which means it has zero Node.js dependencies, a 14KB bundle size, and a router that makes zero allocations per request.
The Router
Hono's RegExpRouter compiles all routes into a single regular expression at startup. At request time, one regex match finds the handler with zero object allocations:
import { Hono } from "hono";
const app = new Hono();
app.get("/users/:id", (c) => {
const id = c.req.param("id");
return c.json({ id });
});
app.post("/users", async (c) => {
const body = await c.req.json();
return c.json({ created: body }, 201);
});
export default app;
Benchmark against Express (Node.js, same hardware): Hono handles ~300,000 req/s vs Express at ~65,000 req/s.
Deploy to Cloudflare Workers
pnpm create cloudflare my-api --template hono
cd my-api
// src/index.ts
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
type Bindings = {
DB: D1Database;
BUCKET: R2Bucket;
};
const app = new Hono<{ Bindings: Bindings }>();
app.use("*", logger());
app.use("/api/*", cors({ origin: "https://yoursite.com" }));
app.get("/api/posts", async (c) => {
const posts = await c.env.DB.prepare("SELECT * FROM posts ORDER BY created_at DESC").all();
return c.json(posts.results);
});
export default app;
wrangler deploy
Built-In Middleware
Hono ships middleware for the most common needs out of the box:
import { jwt } from "hono/jwt";
import { rateLimiter } from "hono/rate-limiter";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
app.use("/protected/*", jwt({ secret: process.env.JWT_SECRET! }));
const createPost = z.object({
title: z.string().min(1),
body: z.string(),
});
app.post("/posts", zValidator("json", createPost), async (c) => {
const data = c.req.valid("json"); // fully typed
return c.json({ ok: true });
});
RPC Mode for Type-Safe Clients
Hono RPC generates a type-safe client from your route definitions — no code generation step needed:
// server: api/routes.ts
const routes = app
.get("/users", (c) => c.json({ users: [] as User[] }))
.post("/users", zValidator("json", createUser), (c) => c.json({ id: "123" }));
export type AppType = typeof routes;
// client: lib/api.ts
import { hc } from "hono/client";
import type { AppType } from "@/api/routes";
const client = hc<AppType>("https://api.example.com");
// Fully typed — autocomplete works
const res = await client.users.$get();
const { users } = await res.json(); // User[]
Running on Bun and Node
// Bun
import { Hono } from "hono";
const app = new Hono();
// ... routes
export default { port: 3000, fetch: app.fetch };
// Node.js
import { serve } from "@hono/node-server";
import { Hono } from "hono";
const app = new Hono();
// ... routes
serve({ fetch: app.fetch, port: 3000 });
The same application code runs on all runtimes — just change the entry point.