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 devhandles everything. Add Vite only if you need asset bundling.
Key Points
- Native Worker - no adapters, no compatibility layers, no framework overhead
c.envgives 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.