TL;DR:

  • Hono is an ultra-lightweight TypeScript web framework (~14KB) that runs natively on Cloudflare Workers, Bun, Deno, Node.js, AWS Lambda, and Fastly without adapters or configuration differences
  • The RPC client generates end-to-end type-safe API calls from your route definitions, eliminating the need for manual type synchronisation between frontend and backend
  • Hono is a practical choice for edge API routes, Cloudflare Workers functions, and any TypeScript project where bundle size and startup latency matter

Every JavaScript web framework claims to be fast. Hono has the benchmark numbers to back it up, but the more interesting property is that the same application code runs on seven different runtime environments without modification. For teams deploying to Cloudflare Workers or building lightweight APIs on Bun, Hono eliminates the adapter and configuration overhead that makes other frameworks awkward on non-Node runtimes.

What Hono Is

Hono is a web application framework built on the Web Standards API — Request, Response, URL, Headers — rather than Node.js specifics. Any runtime that implements the WinterCG (Web-interoperable Runtimes Community Group) standard can run Hono without an adapter layer.

The supported runtimes in 2026 include:

  • Cloudflare Workers
  • Bun
  • Deno
  • Node.js (via the @hono/node-server adapter)
  • AWS Lambda
  • Fastly Compute
  • Vercel Edge Functions

The core package is around 14KB with zero dependencies. Startup time on Cloudflare Workers is measured in milliseconds, which matters for cold starts.

The Basics

Hono’s API is deliberately close to Express in feel, with the routing and middleware model developers already know:

import { Hono } from "hono";

const app = new Hono();

app.get("/", (c) => c.text("Hello, Hono!"));

app.get("/users/:id", (c) => {
  const id = c.req.param("id");
  return c.json({ id, name: "Alice" });
});

app.post("/users", async (c) => {
  const body = await c.req.json();
  // create user...
  return c.json({ created: true }, 201);
});

export default app;

The context object c wraps the request and provides typed helpers for reading params, query strings, headers, and body. Responses are built with c.text(), c.json(), c.html(), or the underlying Response constructor.

For Cloudflare Workers, this is a complete deployment:

// src/index.ts
import { Hono } from "hono";

const app = new Hono<{ Bindings: CloudflareBindings }>();

app.get("/api/kv-value", async (c) => {
  const value = await c.env.MY_KV.get("some-key");
  return c.json({ value });
});

export default app;

The Bindings generic gives you typed access to Cloudflare environment bindings — KV namespaces, R2 buckets, D1 databases, service bindings — with autocomplete in your editor.

Middleware

Hono’s middleware system uses the same (c, next) pattern as other frameworks. Middleware can be applied globally, to a path prefix, or to individual routes.

import { Hono } from "hono";
import { cors } from "hono/cors";
import { bearerAuth } from "hono/bearer-auth";
import { logger } from "hono/logger";
import { timing } from "hono/timing";

const app = new Hono();

// Global middleware
app.use(logger());
app.use(timing());

// Path-scoped middleware
app.use("/api/*", cors());
app.use("/admin/*", bearerAuth({ token: process.env.ADMIN_TOKEN! }));

app.get("/api/public", (c) => c.json({ status: "ok" }));
app.get("/admin/metrics", (c) => c.json({ requests: 1042 }));

export default app;

The built-in middleware library covers CORS, authentication (bearer token, basic auth), rate limiting, caching, JSX rendering, compression, and more. Third-party middleware packages extend this further with Zod validation, OpenAPI generation, and Prometheus metrics.

The RPC Client

The feature that most distinguishes Hono from similar frameworks is the RPC client. When you define routes with typed request and response schemas, Hono generates a client that gives you end-to-end type safety without code generation, proto files, or OpenAPI tooling.

// server/routes/users.ts
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";

const createUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
});

export const usersRoute = new Hono()
  .get("/", (c) => {
    return c.json({ users: [{ id: "1", name: "Alice" }] });
  })
  .post(
    "/",
    zValidator("json", createUserSchema),
    async (c) => {
      const data = c.req.valid("json");
      // data is typed as { name: string; email: string }
      const user = await createUser(data);
      return c.json(user, 201);
    }
  );
// server/index.ts
import { Hono } from "hono";
import { usersRoute } from "./routes/users";

const app = new Hono().route("/users", usersRoute);

export type AppType = typeof app;
export default app;
// client/api.ts
import { hc } from "hono/client";
import type { AppType } from "../server";

const client = hc<AppType>("http://localhost:3000");

// Fully typed — editor autocomplete, type errors on wrong shape
const response = await client.users.$post({
  json: { name: "Bob", email: "bob@example.com" },
});

if (response.ok) {
  const user = await response.json();
  // user is typed from the server's response
}

The key is that AppType is exported from your server and imported by the client — TypeScript infers the request and response types from the route definitions. No tRPC configuration, no code generation step, no schema files to keep in sync.

Request Validation with Zod

The @hono/zod-validator middleware integrates Zod schema validation directly into route handlers. Validation errors return 400 responses automatically; valid data is typed and available via c.req.valid().

import { zValidator } from "@hono/zod-validator";
import { z } from "zod";

const searchSchema = z.object({
  q: z.string().min(1),
  limit: z.coerce.number().min(1).max(100).default(20),
  offset: z.coerce.number().min(0).default(0),
});

app.get("/search", zValidator("query", searchSchema), (c) => {
  const { q, limit, offset } = c.req.valid("query");
  // q: string, limit: number, offset: number — all typed
  return c.json({ results: [] });
});

Validators can be applied to json (request body), query (query parameters), param (path parameters), header, and cookie.

Testing

Hono provides a testClient that makes testing route handlers straightforward without spinning up an HTTP server:

import { describe, it, expect } from "vitest";
import { testClient } from "hono/testing";
import app from "./index";

describe("users API", () => {
  const client = testClient(app);

  it("returns user list", async () => {
    const res = await client.users.$get();
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.users).toHaveLength(1);
  });

  it("creates a user", async () => {
    const res = await client.users.$post({
      json: { name: "Alice", email: "alice@example.com" },
    });
    expect(res.status).toBe(201);
  });
});

The test client uses the same typed interface as the RPC client, so tests break at compile time if you pass the wrong shape — the type check catches problems before the test even runs.

Routing and Sub-Applications

Hono handles nested routes cleanly through app.route(), which mounts a sub-application at a path prefix. This makes it easy to organise large APIs into separate files by resource:

import { Hono } from "hono";
import { usersRoute } from "./routes/users";
import { productsRoute } from "./routes/products";
import { ordersRoute } from "./routes/orders";

const app = new Hono()
  .route("/users", usersRoute)
  .route("/products", productsRoute)
  .route("/orders", ordersRoute);

export type AppType = typeof app;
export default app;

The AppType export captures the full combined route tree, so the RPC client has type information for all routes.

When Hono Makes Sense

Hono earns its place when:

  • You’re deploying to Cloudflare Workers: Hono’s native WinterCG support means no adapter overhead and full access to Workers bindings with TypeScript types.
  • Bundle size or cold start latency matters: At 14KB with zero dependencies, Hono adds almost nothing to a Worker’s bundle.
  • You want end-to-end type safety without the tRPC setup: The RPC client gives you typed API calls from a straightforward route definition, with no separate configuration.
  • You’re writing a lightweight API server on Bun: Hono starts in single-digit milliseconds on Bun and handles tens of thousands of requests per second on modest hardware.

It’s less compelling if you need a full-stack framework with file-based routing, built-in database ORM integration, or a mature plugin ecosystem. For those requirements, Remix, Next.js, or Fastify with its plugin architecture is a better fit. Hono is a routing and middleware layer, not an application framework in the Rails or Django sense.

References