Hono
"Hono: ultra-fast web framework for edge, serverless, and Node.js — middleware, routing, Zod validation, JWT, CORS, RPC client, and JSX support"
Hono is an ultra-fast, lightweight web framework that runs everywhere: Cloudflare Workers, Deno Deploy, Bun, AWS Lambda, Vercel Edge Functions, and Node.js. It uses Web Standard APIs (Request/Response) so code is portable across runtimes without modification. Hono provides first-class TypeScript support with type-safe routing, a rich middleware ecosystem, and a built-in RPC client that gives you tRPC-like type safety without a separate protocol. ## Key Points - Use `hc` RPC client for internal service-to-service or frontend-to-backend calls to get full type safety without codegen. - Chain route definitions on a single Hono instance so the `AppType` export captures all routes for the RPC client. - Validate all inputs with `zValidator` — it integrates cleanly and gives typed access via `c.req.valid()`. - Use environment-specific adapters (`@hono/node-server`, Cloudflare Workers export) but keep handler logic runtime-agnostic. - Scope middleware with path patterns (`/api/*`) rather than applying everything globally. - Use `HTTPException` for expected errors and let `onError` handle unexpected ones uniformly. - Prefer `c.env` for runtime bindings (secrets, KV namespaces) to stay compatible across edge runtimes. - **Importing Node.js built-ins in edge-targeted code.** Hono runs on Web Standards; `fs`, `path`, and `http` do not exist on Workers or Deno Deploy. - **Not chaining routes.** If you define routes with separate `app.get()` calls without chaining, the RPC client type loses track of the routes. - **Blocking the event loop with synchronous work.** Edge runtimes have strict CPU time limits; offload heavy computation. - **Hardcoding secrets.** Use `c.env` bindings or environment variables, never string literals in source. - **Ignoring response status codes.** Always return appropriate HTTP status codes (201 for creation, 404 for not found); the RPC client types include status.
skilldb get api-frameworks-skills/HonoFull skill: 341 linesHono
Core Philosophy
Hono is an ultra-fast, lightweight web framework that runs everywhere: Cloudflare Workers, Deno Deploy, Bun, AWS Lambda, Vercel Edge Functions, and Node.js. It uses Web Standard APIs (Request/Response) so code is portable across runtimes without modification. Hono provides first-class TypeScript support with type-safe routing, a rich middleware ecosystem, and a built-in RPC client that gives you tRPC-like type safety without a separate protocol.
The framework prioritizes zero dependencies, minimal bundle size, and router performance. Its RegExpRouter achieves near-zero overhead for route matching, making it ideal for latency-sensitive edge deployments.
Setup
Basic Application
// Install: npm install hono
// For Node.js: npm install @hono/node-server
// src/index.ts
import { Hono } from "hono";
import { serve } from "@hono/node-server";
const app = new Hono();
app.get("/", (c) => c.text("Hello Hono!"));
app.get("/json", (c) => c.json({ message: "typed response", status: "ok" }));
serve({ fetch: app.fetch, port: 3000 });
// For Cloudflare Workers — just export
// export default app;
// For Bun
// export default { fetch: app.fetch, port: 3000 };
Typed Application with Base Path
import { Hono } from "hono";
type Bindings = {
DATABASE_URL: string;
JWT_SECRET: string;
KV: KVNamespace;
};
type Variables = {
user: { id: string; email: string; role: string };
};
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()
.basePath("/api/v1");
Key Techniques
Routing and Route Groups
import { Hono } from "hono";
const app = new Hono();
// Path parameters
app.get("/users/:id", (c) => {
const id = c.req.param("id");
return c.json({ userId: id });
});
// Wildcards and regex
app.get("/files/*", (c) => c.text(`Path: ${c.req.path}`));
app.get("/posts/:id{[0-9]+}", (c) => c.json({ id: c.req.param("id") }));
// Route groups
const api = new Hono();
const users = new Hono()
.get("/", async (c) => {
const users = await db.user.findMany();
return c.json(users);
})
.post("/", async (c) => {
const body = await c.req.json();
const user = await db.user.create({ data: body });
return c.json(user, 201);
})
.get("/:id", async (c) => {
const user = await db.user.findUnique({ where: { id: c.req.param("id") } });
if (!user) return c.json({ error: "Not found" }, 404);
return c.json(user);
});
api.route("/users", users);
app.route("/api", api);
Middleware
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { prettyJSON } from "hono/pretty-json";
import { secureHeaders } from "hono/secure-headers";
import { timing, startTime, endTime } from "hono/timing";
import { compress } from "hono/compress";
// Built-in middleware
app.use("*", logger());
app.use("*", secureHeaders());
app.use("*", timing());
app.use("*", compress());
app.use("*", prettyJSON());
app.use(
"/api/*",
cors({
origin: ["https://example.com", "https://staging.example.com"],
allowMethods: ["GET", "POST", "PUT", "DELETE"],
allowHeaders: ["Content-Type", "Authorization"],
maxAge: 86400,
})
);
// Custom middleware
const rateLimiter = (limit: number, window: number) => {
const store = new Map<string, { count: number; reset: number }>();
return async (c: Context, next: Next) => {
const key = c.req.header("CF-Connecting-IP") ?? "unknown";
const now = Date.now();
const record = store.get(key);
if (record && record.reset > now && record.count >= limit) {
return c.json({ error: "Rate limit exceeded" }, 429);
}
if (!record || record.reset <= now) {
store.set(key, { count: 1, reset: now + window });
} else {
record.count++;
}
await next();
};
};
app.use("/api/*", rateLimiter(100, 60_000));
Zod Validation with zValidator
// npm install @hono/zod-validator
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const createPostSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(10),
tags: z.array(z.string()).max(5).default([]),
published: z.boolean().default(false),
});
const paginationSchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
sort: z.enum(["asc", "desc"]).default("desc"),
});
const posts = new Hono()
.get("/", zValidator("query", paginationSchema), async (c) => {
const { page, limit, sort } = c.req.valid("query");
const offset = (page - 1) * limit;
const posts = await db.post.findMany({
skip: offset,
take: limit,
orderBy: { createdAt: sort },
});
return c.json({ posts, page, limit });
})
.post("/", zValidator("json", createPostSchema), async (c) => {
const data = c.req.valid("json");
const post = await db.post.create({ data });
return c.json(post, 201);
})
.put(
"/:id",
zValidator("param", z.object({ id: z.string().uuid() })),
zValidator("json", createPostSchema.partial()),
async (c) => {
const { id } = c.req.valid("param");
const data = c.req.valid("json");
const post = await db.post.update({ where: { id }, data });
return c.json(post);
}
);
JWT Authentication
import { jwt } from "hono/jwt";
import { sign, verify } from "hono/jwt";
// JWT middleware for protected routes
app.use(
"/api/protected/*",
jwt({ secret: process.env.JWT_SECRET! })
);
// Login endpoint
app.post("/api/auth/login", zValidator("json", loginSchema), async (c) => {
const { email, password } = c.req.valid("json");
const user = await authenticateUser(email, password);
if (!user) return c.json({ error: "Invalid credentials" }, 401);
const token = await sign(
{ sub: user.id, email: user.email, role: user.role, exp: Math.floor(Date.now() / 1000) + 3600 },
c.env.JWT_SECRET
);
return c.json({ token, user: { id: user.id, email: user.email } });
});
// Access JWT payload in protected routes
app.get("/api/protected/profile", (c) => {
const payload = c.get("jwtPayload");
return c.json({ userId: payload.sub, email: payload.email });
});
RPC Client (Type-Safe Client)
// server.ts — Export the typed app
const routes = app
.get("/api/users", async (c) => {
const users = await db.user.findMany();
return c.json(users);
})
.post(
"/api/users",
zValidator("json", createUserSchema),
async (c) => {
const data = c.req.valid("json");
const user = await db.user.create({ data });
return c.json(user, 201);
}
);
export type AppType = typeof routes;
// client.ts — Fully typed client
import { hc } from "hono/client";
import type { AppType } from "./server";
const client = hc<AppType>("http://localhost:3000");
// Full type safety and autocompletion
const res = await client.api.users.$get();
const users = await res.json(); // typed as User[]
const newUser = await client.api.users.$post({
json: { name: "Alice", email: "alice@example.com" },
});
JSX and HTML Responses
import { Hono } from "hono";
import { html } from "hono/html";
const app = new Hono();
const Layout = (props: { title: string; children: any }) => html`
<!DOCTYPE html>
<html>
<head><title>${props.title}</title></head>
<body>${props.children}</body>
</html>
`;
app.get("/page", (c) => {
return c.html(
Layout({
title: "Dashboard",
children: html`<h1>Welcome</h1><p>Server-rendered HTML with Hono.</p>`,
})
);
});
Error Handling
import { HTTPException } from "hono/http-exception";
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status);
}
console.error("Unhandled error:", err);
return c.json({ error: "Internal server error" }, 500);
});
app.notFound((c) => c.json({ error: "Route not found" }, 404));
// Throwing HTTP exceptions in handlers
app.get("/api/resource/:id", async (c) => {
const resource = await db.resource.findUnique({ where: { id: c.req.param("id") } });
if (!resource) {
throw new HTTPException(404, { message: "Resource not found" });
}
return c.json(resource);
});
Best Practices
- Use
hcRPC client for internal service-to-service or frontend-to-backend calls to get full type safety without codegen. - Chain route definitions on a single Hono instance so the
AppTypeexport captures all routes for the RPC client. - Validate all inputs with
zValidator— it integrates cleanly and gives typed access viac.req.valid(). - Use environment-specific adapters (
@hono/node-server, Cloudflare Workers export) but keep handler logic runtime-agnostic. - Scope middleware with path patterns (
/api/*) rather than applying everything globally. - Use
HTTPExceptionfor expected errors and letonErrorhandle unexpected ones uniformly. - Prefer
c.envfor runtime bindings (secrets, KV namespaces) to stay compatible across edge runtimes.
Anti-Patterns
- Importing Node.js built-ins in edge-targeted code. Hono runs on Web Standards;
fs,path, andhttpdo not exist on Workers or Deno Deploy. - Not chaining routes. If you define routes with separate
app.get()calls without chaining, the RPC client type loses track of the routes. - Blocking the event loop with synchronous work. Edge runtimes have strict CPU time limits; offload heavy computation.
- Hardcoding secrets. Use
c.envbindings or environment variables, never string literals in source. - Ignoring response status codes. Always return appropriate HTTP status codes (201 for creation, 404 for not found); the RPC client types include status.
- Using body parsing without validation. Raw
c.req.json()givesany; always pair with Zod validation for safety.
Install this skill directly: skilldb add api-frameworks-skills
Related Skills
Elysia
"Elysia: Bun-native web framework with type-safe routing, TypeBox validation, plugins, Eden treaty client, lifecycle hooks, and Swagger documentation"
Express.js
"Express.js with TypeScript: routing, middleware patterns, error handling, validation, authentication, static files, CORS, and production-ready configuration"
Fastify
Fastify: high-performance Node.js web framework with schema-based validation, logging, plugin architecture, and TypeScript support
Apollo GraphQL
"Apollo GraphQL: schema design, resolvers, Apollo Server, Apollo Client with React, useQuery/useMutation, caching strategies, subscriptions, and codegen"
Koa
Koa: lightweight Node.js framework by the Express team with async/await middleware, context object, and composable architecture
NestJS
NestJS: progressive Node.js framework with decorators, dependency injection, modules, guards, pipes, and interceptors for scalable APIs