Hono on Cloudflare Workers

Hono is a lightweight web framework built for edge runtimes. It runs natively on Cloudflare Workers with zero adapters needed. If you’re building APIs, microservices, or backend-for-frontend patterns, Hono is the most natural fit for Workers.

Create a New Project

npm create cloudflare@latest my-hono-api -- --framework=hono

Or start from scratch:

npm create hono@latest my-hono-api
# Select "cloudflare-workers" template

Project Structure

my-hono-api/
├── src/
│   └── index.ts         # Worker entry point + routes
├── wrangler.jsonc       # Cloudflare config
└── package.json

That’s it. No adapters, no build plugins, no framework config files.

Basic API

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

type Bindings = {
  MY_KV: KVNamespace;
  MY_DB: D1Database;
};

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

app.get("/", (c) => c.text("Hello from Workers"));

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

app.post("/api/data", async (c) => {
  const body = await c.req.json();
  await c.env.MY_KV.put("key", JSON.stringify(body));
  return c.json({ ok: true });
});

export default app;

Middleware

Hono has a rich middleware ecosystem. All of it works on Workers.

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

const app = new Hono();

// Apply globally
app.use("*", logger());
app.use("/api/*", cors());

// Apply to specific routes
app.use("/admin/*", bearerAuth({ token: "secret" }));

app.get("/api/public", (c) => c.json({ public: true }));
app.get("/admin/dashboard", (c) => c.json({ admin: true }));

export default app;

D1 Database Example

app.get("/api/users", async (c) => {
  const { results } = await c.env.MY_DB
    .prepare("SELECT id, name, email FROM users LIMIT 50")
    .all();
  return c.json(results);
});

app.post("/api/users", async (c) => {
  const { name, email } = await c.req.json();
  const result = await c.env.MY_DB
    .prepare("INSERT INTO users (name, email) VALUES (?, ?)")
    .bind(name, email)
    .run();
  return c.json({ id: result.meta.last_row_id }, 201);
});

Serving HTML (JSX)

Hono supports JSX rendering, but it’s server-side only (no hydration, no client interactivity).

import { Hono } from "hono";

const app = new Hono();

app.get("/", (c) => {
  return c.html(
    <html>
      <body>
        <h1>Server-rendered HTML</h1>
        <p>No client JavaScript shipped.</p>
      </body>
    </html>
  );
});

export default app;

Tip: For interactive UIs, pair Hono with htmx or use Hono as an API backend for a separate SPA.

Wrangler Config

// wrangler.jsonc
{
  "name": "my-hono-api",
  "main": "src/index.ts",
  "compatibility_flags": ["nodejs_compat"],
  "kv_namespaces": [
    { "binding": "MY_KV", "id": "your-kv-id" }
  ],
  "d1_databases": [
    { "binding": "MY_DB", "database_id": "your-db-id", "database_name": "mydb" }
  ]
}

Dev and Deploy

# Development (uses wrangler's miniflare, runs in workerd)
npx wrangler dev

# Deploy
npx wrangler deploy

Tip: Hono doesn’t need Vite or a build step for pure API projects. wrangler dev handles everything. Add Vite only if you need asset bundling.

Key Points

  • Native Worker - no adapters, no compatibility layers, no framework overhead
  • c.env gives typed access to all Cloudflare bindings
  • Middleware ecosystem (cors, auth, logger, rate-limit, etc.) all works on Workers
  • 14KB gzipped runtime, starts in microseconds
  • Best for: APIs, microservices, BFF pattern, webhook handlers
  • Not for: full client-side interactivity (no hydration, no client routing)

Gotcha: Hono’s JSX is server-only. If you render HTML with JSX and expect React-style interactivity, you’ll be disappointed. For interactive UIs, use Hono as an API and a separate frontend.