Hono - Ultrafast Web Framework
Overview
Hono is a small, simple, and ultrafast web framework built on Web Standards. It runs on Cloudflare Workers, Deno, Bun, Node.js, and more with the same codebase. The name means "flame" in Japanese.
Key Features:
- Built on Web Standards (Request/Response/fetch)
- Multi-runtime: Cloudflare Workers, Deno, Bun, Node.js, Vercel, AWS Lambda
- Ultrafast routing with RegExpRouter
- First-class TypeScript support
- Lightweight (~14KB minified)
- Rich middleware ecosystem
Installation:
# Create new project (recommended)
npm create hono@latest my-app
# Or install in existing project
npm install hono
# Runtime-specific adapters
npm install @hono/node-server # Node.js
When to Use This Skill
Use Hono when:
- Building APIs for edge/serverless environments (Cloudflare Workers, Vercel Edge)
- Need multi-runtime portability (same code on Bun, Deno, Node.js)
- Want TypeScript-first development with excellent type inference
- Building lightweight, high-performance APIs
- Need built-in middleware for common patterns (CORS, auth, compression)
Hono vs Other Frameworks:
- Hono: Multi-runtime, Web Standards, ultrafast, edge-optimized
- Express: Node.js only, larger ecosystem, slower
- Fastify: Node.js only, schema-based, good performance
- Elysia: Bun only, excellent performance, different API style
Core Concepts
Creating an Application
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("Hello Hono!"));
export default app;
With TypeScript Generics (for bindings/variables):
type Bindings = {
DATABASE_URL: string;
API_KEY: string;
};
type Variables = {
user: { id: string; name: string };
};
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>();
The Context Object (c)
The context c provides access to request data and response methods:
app.get("/users/:id", async (c) => {
// Request data
const id = c.req.param("id"); // Path parameter
const query = c.req.query("sort"); // Query parameter ?sort=asc
const queries = c.req.queries("tags"); // Multiple: ?tags=a&tags=b
const header = c.req.header("Authorization");
const body = await c.req.json(); // JSON body
const form = await c.req.formData(); // Form data
// Environment (Cloudflare Workers bindings)
const db = c.env.DATABASE_URL;
// Custom variables (set by middleware)
const user = c.get("user");
// Response methods
return c.text("Plain text");
return c.json({ id, name: "User" });
return c.html("<h1>Hello</h1>");
return c.redirect("/login");
return c.notFound();
});
Response Methods
// Text response
c.text("Hello", 200);
// JSON response
c.json({ message: "Success" }, 201);
c.json({ error: "Not found" }, 404);
// HTML response
c.html("<h1>Hello</h1>");
// Redirect
c.redirect("/login"); // 302 default
c.redirect("/login", 301); // Permanent redirect
// Headers
c.header("X-Custom", "value");
c.header("Cache-Control", "max-age=3600");
// Streaming
c.streamText(async (stream) => {
await stream.write("Hello ");
await stream.write("World!");
});
// Raw Response
return new Response("Raw", { status: 200 });
Routing Patterns
Basic Routing
const app = new Hono();
// HTTP methods
app.get("/users", getUsers);
app.post("/users", createUser);
app.put("/users/:id", updateUser);
app.delete("/users/:id", deleteUser);
app.patch("/users/:id", patchUser);
// All methods
app.all("/webhook", handleWebhook);
// Custom methods
app.on("PURGE", "/cache", purgeCache);
app.on(["GET", "POST"], "/form", handleForm);
Path Parameters
// Single parameter
app.get("/users/:id", (c) => {
const id = c.req.param("id");
return c.json({ id });
});
// Multiple parameters
app.get("/posts/:postId/comments/:commentId", (c) => {
const { postId, commentId } = c.req.param();
return c.json({ postId, commentId });
});
// Optional parameter
app.get("/api/animal/:type?", (c) => {
const type = c.req.param("type") || "all";
return c.json({ type });
});
// Regex validation
app.get("/posts/:id{[0-9]+}", (c) => {
const id = c.req.param("id"); // Only numeric IDs
return c.json({ id });
});
// Wildcards
app.get("/files/*", (c) => {
const path = c.req.param("*"); // Everything after /files/
return c.text(`File: ${path}`);
});
Route Grouping
// Using app.route()
const api = new Hono();
api.get("/users", getUsers);
api.get("/posts", getPosts);
const app = new Hono();
app.route("/api/v1", api); // /api/v1/users, /api/v1/posts
// Using basePath()
const v2 = new Hono().basePath("/api/v2");
v2.get("/users", getUsers); // /api/v2/users
// Chaining
app.get("/a", handlerA).post("/b", handlerB).delete("/c", handlerC);
Route Organization (Multi-File)
// routes/users.ts
import { Hono } from "hono";
const users = new Hono();
users.get("/", async (c) => {
return c.json({ users: [] });
});
users.post("/", async (c) => {
const body = await c.req.json();
return c.json({ created: body }, 201);
});
users.get("/:id", async (c) => {
const id = c.req.param("id");
return c.json({ id });
});
export default users;
// app.ts
import { Hono } from "hono";
import users from "./routes/users";
import posts from "./routes/posts";
const app = new Hono();
app.route("/users", users);
app.route("/posts", posts);
export default app;
Handler Patterns
Inline Handlers
// Simple handler
app.get("/hello", (c) => c.text("Hello!"));
// Async handler
app.get("/users", async (c) => {
const users = await fetchUsers();
return c.json({ users });
});
// Multiple handlers (middleware chain)
app.get("/admin", authenticate, authorize, (c) => {
return c.json({ admin: true });
});
Using Factory for Type-Safe Handlers
import { createFactory } from "hono/factory";
const factory = createFactory<{ Bindings: Bindings }>();
// Create typed handler
const getUser = factory.createHandlers(async (c) => {
const id = c.req.param("id");
const db = c.env.DATABASE_URL; // Typed!
return c.json({ id });
});
app.get("/users/:id", ...getUser);
Error Handling
Built-in Error Handling
import { HTTPException } from "hono/http-exception";
app.get("/users/:id", async (c) => {
const user = await findUser(c.req.param("id"));
if (!user) {
throw new HTTPException(404, { message: "User not found" });
}
return c.json(user);
});
// Global error handler
app.onError((err, c) => {
console.error(`${err}`);
if (err instanceof HTTPException) {
return err.getResponse();
}
return c.json({ error: "Internal Server Error" }, 500);
});
// Not found handler
app.notFound((c) => {
return c.json({ error: "Route not found" }, 404);
});
Custom Error Classes
class ValidationError extends HTTPException {
constructor(errors: string[]) {
super(400, {
message: "Validation failed",
cause: errors,
});
}
}
class AuthenticationError extends HTTPException {
constructor() {
super(401, { message: "Authentication required" });
}
}
Runtime-Specific Exports
Cloudflare Workers
// src/index.ts
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("Hello Cloudflare!"));
export default app;
Node.js
// src/index.ts
import { serve } from "@hono/node-server";
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("Hello Node!"));
serve({
fetch: app.fetch,
port: 3000,
});
Bun
// src/index.ts
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("Hello Bun!"));
export default {
port: 3000,
fetch: app.fetch,
};
Deno
// main.ts
import { Hono } from "npm:hono";
const app = new Hono();
app.get("/", (c) => c.text("Hello Deno!"));
Deno.serve(app.fetch);
Best Practices
Write Handlers Inline (Not Controllers)
// CORRECT: Inline handlers with proper type inference
app.get("/users/:id", async (c) => {
const id = c.req.param("id"); // Type: string
return c.json({ id });
});
// AVOID: Controller-style (loses type inference)
class UserController {
getUser(c: Context) {
const id = c.req.param("id"); // Type: string | undefined
return c.json({ id });
}
}
Use Modular Routes
// CORRECT: Split routes by domain
// routes/users.ts
export const users = new Hono().get("/", listUsers).post("/", createUser).get("/:id", getUser);
// app.ts
app.route("/users", users);
Type Everything
// Define your environment bindings
type Bindings = {
DATABASE_URL: string;
JWT_SECRET: string;
MY_KV: KVNamespace;
};
// Pass to Hono
const app = new Hono<{ Bindings: Bindings }>();
// Now c.env is fully typed
app.get("/", (c) => {
const url = c.env.DATABASE_URL; // string
const kv = c.env.MY_KV; // KVNamespace
});
Quick Reference
Common Context Methods
| Method | Description | Example |
|---|---|---|
c.req.param(name) |
Get path parameter | c.req.param('id') |
c.req.query(name) |
Get query parameter | c.req.query('page') |
c.req.header(name) |
Get request header | c.req.header('Authorization') |
c.req.json() |
Parse JSON body | await c.req.json() |
c.req.formData() |
Parse form data | await c.req.formData() |
c.text(str, status) |
Text response | c.text('OK', 200) |
c.json(obj, status) |
JSON response | c.json({}, 201) |
c.html(str) |
HTML response | c.html('<h1>Hi</h1>') |
c.redirect(url) |
Redirect | c.redirect('/login') |
c.header(k, v) |
Set response header | c.header('X-Custom', 'val') |
c.set(key, val) |
Set context variable | c.set('user', user) |
c.get(key) |
Get context variable | c.get('user') |
c.env |
Environment bindings | c.env.API_KEY |
HTTP Methods
app.get(path, ...handlers);
app.post(path, ...handlers);
app.put(path, ...handlers);
app.delete(path, ...handlers);
app.patch(path, ...handlers);
app.options(path, ...handlers);
app.head(path, ...handlers);
app.all(path, ...handlers);
app.on(method, path, ...handlers);
Related Skills
- hono-middleware - Middleware patterns and composition
- hono-validation - Request validation with Zod
- hono-rpc - Type-safe RPC client
- hono-testing - Testing patterns
- hono-jsx - Server-side JSX rendering
- hono-cloudflare - Cloudflare Workers deployment
Version: Hono 4.x Last Updated: January 2025 License: MIT