Hono Reference: Routing, Middleware, Validation, Cloudflare Workers & RPC Mode
Hono is a web framework built on the Web Standards API (Request/Response/URL) — the same interface used by Cloudflare Workers, Deno, Bun, and Node.js 18+. One codebase runs everywhere. It’s fast, has a very small bundle (~14kb), and has first-class TypeScript support. The key differentiator from Express: Hono is edge-native. No Node.js stream APIs, no req.body magic — everything is standard Request/Response.
1. Routing & Context
Route handlers, path params, query strings, c.req / c.res, and method chaining
// npm install hono
import { Hono } from "hono";
const app = new Hono();
// Basic routes:
app.get("/", (c) => c.text("Hello Hono!"));
app.get("/json", (c) => c.json({ status: "ok" }));
app.post("/users", async (c) => {
const body = await c.req.json();
return c.json({ created: body }, 201);
});
// Path parameters:
app.get("/users/:id", (c) => {
const id = c.req.param("id");
return c.json({ id });
});
// Multiple params:
app.get("/orgs/:org/repos/:repo", (c) => {
const { org, repo } = c.req.param();
return c.json({ org, repo });
});
// Query strings:
app.get("/search", (c) => {
const q = c.req.query("q");
const page = c.req.query("page") ?? "1";
return c.json({ q, page: parseInt(page) });
});
// Headers:
app.get("/protected", (c) => {
const token = c.req.header("Authorization");
return c.json({ token });
});
// Method chaining:
const api = new Hono();
api.get("/users", listUsers)
.post("/users", createUser)
.get("/users/:id", getUser)
.put("/users/:id", updateUser)
.delete("/users/:id", deleteUser);
2. Middleware
Built-in middleware (logger, cors, jwt, bearer-auth), custom middleware, and app.use()
import { Hono } from "hono";
import { logger } from "hono/logger";
import { cors } from "hono/cors";
import { jwt } from "hono/jwt";
import { bearerAuth } from "hono/bearer-auth";
import { prettyJSON } from "hono/pretty-json";
const app = new Hono();
// Global middleware (runs on every request):
app.use("*", logger()); // logs method + path + status + time
app.use("*", cors()); // CORS headers (configure origin/methods/headers)
app.use("*", prettyJSON()); // format JSON with ?pretty=true
// JWT middleware (protects routes below it):
app.use("/api/*", jwt({ secret: Deno.env.get("JWT_SECRET")! }));
// Bearer token auth:
app.use("/admin/*", bearerAuth({ token: process.env.API_TOKEN! }));
// Custom middleware:
const authMiddleware = async (c: Context, next: Next) => {
const token = c.req.header("Authorization")?.split(" ")[1];
if (!token) return c.json({ error: "Unauthorized" }, 401);
try {
const payload = await verify(token, "secret");
c.set("user", payload); // pass to handler via context
await next();
} catch {
return c.json({ error: "Invalid token" }, 401);
}
};
app.use("/api/*", authMiddleware);
// Access context variables in handler:
app.get("/api/me", (c) => {
const user = c.get("user");
return c.json({ user });
});
// CORS configuration:
app.use("*", cors({
origin: ["https://app.example.com", "http://localhost:3000"],
allowMethods: ["GET", "POST", "PUT", "DELETE"],
allowHeaders: ["Content-Type", "Authorization"],
credentials: true,
}));
3. Validation with Zod Validator
zValidator middleware, validate body/params/query, typed handler input
// npm install @hono/zod-validator zod
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const app = new Hono();
const createUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
role: z.enum(["admin", "user"]).default("user"),
});
const userParamsSchema = z.object({
id: z.string().uuid(),
});
// Validate request body:
app.post(
"/users",
zValidator("json", createUserSchema),
async (c) => {
const data = c.req.valid("json"); // typed as CreateUser — validated + parsed
// data.name, data.email, data.role are all typed correctly
const user = await db.users.create({ data });
return c.json(user, 201);
}
);
// Validate path params:
app.get(
"/users/:id",
zValidator("param", userParamsSchema),
async (c) => {
const { id } = c.req.valid("param"); // typed as { id: string }
const user = await db.users.findUnique({ where: { id } });
if (!user) return c.json({ error: "Not found" }, 404);
return c.json(user);
}
);
// Validate query params:
const searchSchema = z.object({
q: z.string().optional(),
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().max(100).default(20),
});
app.get("/search", zValidator("query", searchSchema), (c) => {
const { q, page, limit } = c.req.valid("query");
return c.json({ q, page, limit });
});
4. Deployment — Cloudflare Workers, Node.js & Bun
serve() for Node.js and Bun, Cloudflare Workers export default, environment bindings
// Cloudflare Workers (wrangler.toml + same Hono app):
// wrangler.toml: name = "my-api", main = "src/index.ts"
// npm install wrangler -D; npx wrangler dev; npx wrangler deploy
// The app is the same — just export default:
import { Hono } from "hono";
const app = new Hono<{ Bindings: { DB: D1Database; KV: KVNamespace } }>();
app.get("/users", async (c) => {
const users = await c.env.DB.prepare("SELECT * FROM users").all();
return c.json(users.results);
});
app.get("/cache/:key", async (c) => {
const val = await c.env.KV.get(c.req.param("key"));
return c.json({ val });
});
export default app; // Cloudflare Workers entry point
// Node.js (npm install @hono/node-server):
import { serve } from "@hono/node-server";
serve({ fetch: app.fetch, port: 3000 }, (info) => {
console.log(`Listening on http://localhost:${info.port}`);
});
// Bun (built-in):
export default { port: 3000, fetch: app.fetch }; // bun run src/index.ts
// Deno:
Deno.serve(app.fetch);
// Path grouping and sub-applications:
const v1 = new Hono();
v1.get("/users", listUsers);
v1.post("/users", createUser);
const v2 = new Hono();
v2.get("/users", listUsersV2);
const root = new Hono();
root.route("/api/v1", v1);
root.route("/api/v2", v2);
export default root;
5. RPC Mode & Streaming
Hono RPC for end-to-end type safety, streaming responses, and Server-Sent Events
// Hono RPC — type-safe client from server routes (like tRPC but lighter):
import { Hono } from "hono";
import { hc } from "hono/client";
import { zValidator } from "@hono/zod-validator";
const app = new Hono()
.get("/users", (c) => c.json([{ id: "1", name: "Alice" }]))
.post("/users",
zValidator("json", createUserSchema),
async (c) => {
const data = c.req.valid("json");
return c.json({ id: "2", ...data }, 201);
}
);
export type AppType = typeof app; // export for client
// Client (in frontend — no runtime code from server):
import type { AppType } from "./api";
const client = hc<AppType>("http://localhost:3000");
// Fully typed:
const users = await client.users.$get(); // GET /users
const data = await users.json(); // User[]
const res = await client.users.$post({ // POST /users
json: { name: "Bob", email: "bob@x.com" }
});
// Streaming responses:
import { stream, streamText, streamSSE } from "hono/streaming";
app.get("/stream", (c) => stream(c, async (stream) => {
for (let i = 0; i < 5; i++) {
await stream.write(`chunk ${i}\n`);
await stream.sleep(500);
}
}));
// Server-Sent Events (SSE):
app.get("/events", (c) => streamSSE(c, async (stream) => {
for (let i = 0; i < 10; i++) {
await stream.writeSSE({ data: JSON.stringify({ count: i }), event: "update" });
await stream.sleep(1000);
}
}));
Track Node.js, Deno, and Bun releases at ReleaseRun. Related: Deno 2 Reference | Bun Reference | Express.js Reference
🔍 Free tool: npm Package Health Checker — check Hono and related edge runtime packages for known CVEs and active maintenance.
Founded
2023 in London, UK
Contact
hello@releaserun.com