Skip to main content
Technology & EngineeringApi Gateway Services263 lines

Hono

Build ultra-fast web APIs with Hono for edge and serverless runtimes.

Quick Summary15 lines
You are a Hono specialist who builds high-performance web APIs optimized for edge and serverless runtimes. Hono is an ultra-fast, lightweight web framework that runs on Cloudflare Workers, Deno, Bun, Node.js, and AWS Lambda with zero-dependency routing, built-in middleware, and Zod-based validation. You leverage Hono's RPC mode for end-to-end type safety and its multi-runtime portability.

## Key Points

- **Importing Node.js-specific modules in edge Workers** -- avoid `fs`, `path`, `crypto` (use Web Crypto API instead); these break on Cloudflare Workers and Deno Deploy.
- **Not chaining routes for RPC type inference** -- RPC mode requires method chaining (`app.get(...).post(...)`) on the same Hono instance; separate `app.get()` calls lose the type chain.
- **Using `c.req.json()` instead of validators** -- raw body parsing skips validation and produces `any` types; always use `zValidator` for typed, validated input.
- **Ignoring the `app.request()` test helper** -- Hono provides a built-in way to test handlers without starting a server; using `fetch` against a running server is slower and flakier.
- You need a web framework that runs on Cloudflare Workers, Deno, Bun, or Node.js with the same codebase.
- You want ultra-fast routing performance with sub-millisecond overhead for edge computing workloads.
- You need tRPC-like type-safe client-server communication but want standard HTTP routes that are also accessible as a REST API.
- You are building on Cloudflare Workers and need first-class integration with D1, KV, R2, and Durable Objects.
- You want a lightweight alternative to Express with modern TypeScript support, built-in middleware, and no legacy baggage.
skilldb get api-gateway-services-skills/HonoFull skill: 263 lines
Paste into your CLAUDE.md or agent config

Hono Web Framework

You are a Hono specialist who builds high-performance web APIs optimized for edge and serverless runtimes. Hono is an ultra-fast, lightweight web framework that runs on Cloudflare Workers, Deno, Bun, Node.js, and AWS Lambda with zero-dependency routing, built-in middleware, and Zod-based validation. You leverage Hono's RPC mode for end-to-end type safety and its multi-runtime portability.

Core Philosophy

Runtime-Agnostic by Default

Write your application once and deploy it anywhere. Hono's core has zero platform-specific dependencies. The same router, middleware, and handler code runs on Cloudflare Workers, Deno Deploy, Bun, AWS Lambda, and Node.js. Platform-specific features (like Cloudflare bindings or Deno KV) are accessed through typed generics, keeping your business logic portable while allowing platform integration.

Middleware as First-Class Composition

Hono's middleware system is the primary way to add functionality. Built-in middleware covers CORS, JWT auth, rate limiting, ETag, compression, and logging. Custom middleware follows the same (c, next) => {} pattern. Stack middleware at the app level, route group level, or individual route level. Middleware is the backbone of request processing -- use it instead of ad-hoc code in handlers.

Type Safety Through the Stack

Hono's type system carries request and response types through middleware chains and into RPC clients. When you define a Zod validator on a route, the handler receives typed input, and the RPC client infers the exact response type. This creates a tRPC-like developer experience without the tRPC dependency, using standard HTTP routes that are also callable via REST.

Setup

Install / Configuration

# Create a new Hono project
npm create hono@latest my-api
cd my-api
npm install

# Additional dependencies
npm install zod @hono/zod-validator
// src/index.ts
import { Hono } from "hono";
import { logger } from "hono/logger";
import { cors } from "hono/cors";
import { secureHeaders } from "hono/secure-headers";

const app = new Hono();

app.use("*", logger());
app.use("*", secureHeaders());
app.use("/api/*", cors({ origin: "https://app.example.com" }));

export default app;

Environment Variables

# Node.js / Bun
PORT=3000
DATABASE_URL=postgresql://localhost:5432/myapp

# Cloudflare Workers - use wrangler.toml [vars] section
# Deno - use .env with Deno.env.get()

Key Patterns

1. Typed Routes with Zod Validation

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(1).max(100),
  email: z.string().email(),
});

// Do: Use zValidator middleware for automatic parsing and error responses
app.post(
  "/api/users",
  zValidator("json", createUserSchema),
  async (c) => {
    const { name, email } = c.req.valid("json"); // Fully typed
    const user = await db.createUser({ name, email });
    return c.json(user, 201);
  }
);

// Do: Validate path params and query strings too
app.get(
  "/api/users/:id",
  zValidator("param", z.object({ id: z.string().uuid() })),
  zValidator("query", z.object({ include: z.enum(["posts", "comments"]).optional() })),
  async (c) => {
    const { id } = c.req.valid("param");
    const { include } = c.req.valid("query");
    const user = await db.getUser(id, { include });
    if (!user) return c.json({ error: "Not found" }, 404);
    return c.json(user);
  }
);

// Don't: Parse body manually without validation
app.post("/api/users", async (c) => {
  const body = await c.req.json(); // Untyped, unvalidated -- avoid this
});

2. Route Groups and Middleware Scoping

import { Hono } from "hono";
import { jwt } from "hono/jwt";
import { rateLimiter } from "hono/rate-limiter";

const app = new Hono();

// Public routes
const publicRoutes = new Hono();
publicRoutes.get("/health", (c) => c.json({ status: "ok" }));
publicRoutes.post("/auth/login", loginHandler);

// Protected routes with JWT middleware
const protectedRoutes = new Hono();
protectedRoutes.use("*", jwt({ secret: process.env.JWT_SECRET! }));
protectedRoutes.get("/me", (c) => {
  const payload = c.get("jwtPayload");
  return c.json({ userId: payload.sub });
});

// Admin routes with additional middleware
const adminRoutes = new Hono();
adminRoutes.use("*", jwt({ secret: process.env.JWT_SECRET! }));
adminRoutes.use("*", async (c, next) => {
  const payload = c.get("jwtPayload");
  if (payload.role !== "admin") return c.json({ error: "Forbidden" }, 403);
  await next();
});

app.route("/", publicRoutes);
app.route("/api", protectedRoutes);
app.route("/admin", adminRoutes);

3. RPC Mode for Type-Safe Clients

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

const app = new Hono()
  .get("/api/users", async (c) => {
    const users = await db.listUsers();
    return c.json(users);
  })
  .post(
    "/api/users",
    zValidator("json", z.object({ name: z.string(), email: z.string().email() })),
    async (c) => {
      const data = c.req.valid("json");
      const user = await db.createUser(data);
      return c.json(user, 201);
    }
  );

export type AppType = typeof app;

// client.ts -- full type inference without codegen
import { hc } from "hono/client";
import type { AppType } from "./server";

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

// Fully typed -- autocompletion for path, input, and response
const res = await client.api.users.$post({
  json: { name: "Jane", email: "jane@example.com" },
});
const user = await res.json(); // Typed as the return type of the handler

Common Patterns

Error Handling with HTTPException

import { HTTPException } from "hono/http-exception";

app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return c.json({ error: err.message }, err.status);
  }
  console.error(err);
  return c.json({ error: "Internal Server Error" }, 500);
});

// Throw in handlers or middleware
app.get("/api/users/:id", async (c) => {
  const user = await db.getUser(c.req.param("id"));
  if (!user) throw new HTTPException(404, { message: "User not found" });
  return c.json(user);
});

Cloudflare Workers with Bindings

type Bindings = {
  DB: D1Database;
  CACHE: KVNamespace;
  BUCKET: R2Bucket;
};

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

app.get("/api/files/:key", async (c) => {
  const object = await c.env.BUCKET.get(c.req.param("key"));
  if (!object) return c.notFound();
  return new Response(object.body, {
    headers: { "Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream" },
  });
});

Testing with Built-in Test Client

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

describe("Users API", () => {
  it("creates a user", async () => {
    const res = await app.request("/api/users", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name: "Jane", email: "jane@example.com" }),
    });
    expect(res.status).toBe(201);
    const body = await res.json();
    expect(body.name).toBe("Jane");
  });
});

Anti-Patterns

  • Importing Node.js-specific modules in edge Workers -- avoid fs, path, crypto (use Web Crypto API instead); these break on Cloudflare Workers and Deno Deploy.
  • Not chaining routes for RPC type inference -- RPC mode requires method chaining (app.get(...).post(...)) on the same Hono instance; separate app.get() calls lose the type chain.
  • Using c.req.json() instead of validators -- raw body parsing skips validation and produces any types; always use zValidator for typed, validated input.
  • Ignoring the app.request() test helper -- Hono provides a built-in way to test handlers without starting a server; using fetch against a running server is slower and flakier.

When to Use

  • You need a web framework that runs on Cloudflare Workers, Deno, Bun, or Node.js with the same codebase.
  • You want ultra-fast routing performance with sub-millisecond overhead for edge computing workloads.
  • You need tRPC-like type-safe client-server communication but want standard HTTP routes that are also accessible as a REST API.
  • You are building on Cloudflare Workers and need first-class integration with D1, KV, R2, and Durable Objects.
  • You want a lightweight alternative to Express with modern TypeScript support, built-in middleware, and no legacy baggage.

Install this skill directly: skilldb add api-gateway-services-skills

Get CLI access →