---
name: hono-edge-api-2026
description: Build Hono-based HTTP APIs that run on Node, Bun, Deno, Cloudflare Workers, and Vercel Edge with the same code. Use when scaffolding a new API service, adding routes, wiring middleware (CORS, JWT, rate-limit), or porting from Express/Fastify. Covers RPC client export for end-to-end type safety.
category: backend
version: 0.1.0
tags: [hono, edge, api, server, typescript]
recommended_npm: ["hono", "@hono/node-server", "@hono/zod-validator", "zod", "@hono/swagger-ui"]
license: MIT
author: claude-code-skills
---

Hono is the right default for new Node/edge APIs in 2026: tiny (~14kB), runs everywhere, has a typed RPC client that gives you end-to-end type safety without code generation.

## Project skeleton

```
src/
  app.ts          # Hono instance + route mounting + middleware
  server.ts       # Node entry: serve(app)
  routes/
    health.ts
    products.ts
  lib/
    db.ts
    auth.ts
  rpc.ts          # Export types for the client (frontend imports this)
```

## Composition

```ts
// src/app.ts
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { secureHeaders } from "hono/secure-headers";
import { health } from "./routes/health.js";
import { products } from "./routes/products.js";

export const app = new Hono()
  .use("*", logger())
  .use("*", secureHeaders())
  .use("*", cors({ origin: (o) => o ?? "*", credentials: true }))
  .route("/v1", health)
  .route("/v1/products", products);

export type AppType = typeof app; // for the RPC client
```

```ts
// src/routes/products.ts
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
import { db } from "../lib/db.js";

export const products = new Hono()
  .get("/", async (c) => c.json(await db.listProducts()))
  .post(
    "/",
    zValidator("json", z.object({ name: z.string().min(1), price: z.number().int().min(0) })),
    async (c) => {
      const body = c.req.valid("json");
      const created = await db.createProduct(body);
      return c.json(created, 201);
    },
  );
```

## Typed RPC client

```ts
// src/rpc.ts (exported to clients)
import { hc } from "hono/client";
import type { AppType } from "./app.js";
export const apiClient = (baseUrl: string) => hc<AppType>(baseUrl);

// In the frontend:
const api = apiClient("https://api.example.com");
const res = await api.v1.products.$get();
//          ^? typed: Response with the exact response schema
const data = await res.json();
//           ^? typed
```

## Middleware patterns to use

- **`hono/jwt`** for verifying tokens.
- **`hono/cache`** with `Cache-Control` semantics — works on edge runtimes.
- **Custom rate-limit** with the platform's KV (`hono/rate-limiter` for Node; Cloudflare KV / Upstash for edge).
- **`hono/timing`** in dev to expose `Server-Timing` headers — invaluable for profiling.

## Multi-runtime gotchas

- Don't import `node:fs`/`node:crypto` at module scope if you'll deploy to Cloudflare Workers — use Web Crypto (`crypto.subtle`) and fetch-based storage.
- `c.env` is the platform bindings object on Workers; on Node it's `process.env`. Abstract through a `getEnv(c)` helper.
- `c.executionCtx.waitUntil(...)` for background work — works on Workers; no-op shim on Node.

## Anti-patterns

- ❌ Returning raw objects without `c.json()` — you lose `Content-Type` and status code intent.
- ❌ Validating request bodies with hand-written `if (typeof body.x !== 'string')` — use `zValidator` or `@hono/typebox-validator`.
- ❌ Mixing Express middleware (`(req, res, next)`) — Hono uses `(c, next)` and won't call legacy middleware.
- ❌ Calling `c.req.json()` then validating with Zod separately — `zValidator` does both atomically.
- ❌ Setting CORS to `origin: "*"` with `credentials: true` — browsers reject this; specify allowed origins explicitly.

## Quality gates

- `pnpm build` produces a single bundle ≤ 200kB for the worker target.
- `wrangler dev` and `node dist/server.js` both start in < 500ms.
- The RPC client compiles in the consuming repo without `any` leaks.
- Health endpoint returns under 5ms cold-cache on the chosen runtime.
