As the Cloudflare Workers ecosystem has grown, the need for an edge-first web framework has become increasingly critical. Hono.js emerged right at this point: with a sub-12KB bundle size, ~150K req/sec throughput, and the ability to run on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda, and Vercel Edge from a single codebase, it became the fastest-growing backend framework of the 2024-2026 period.
In this guide we'll cover Hono's architecture, its router system, the middleware ecosystem, RPC mode, and real production deployment scenarios.
Pro Tip: The most common mistake with Hono is setting up middleware order incorrectly. app.use() calls must come before the route definitions; middleware defined afterward only applies to the routes that follow it. This differs from Node.js/Express behavior.Table of Contents
- 1. Why Hono? The Competitive Edge
- 2. Router Architecture: RegExpRouter, TrieRouter, SmartRouter
- RegExpRouter
- TrieRouter
- SmartRouter (Default)
- 3. Multi-Runtime Compatibility
- 4. Middleware Ecosystem
- 5. JSX Renderer and SSR
- 6. RPC Mode: a Type-Safe Client
- 7. Validator and OpenAPI Integration
- 8. Testing Strategies
- 9. Production Deployment Patterns
- Cloudflare Workers (Wrangler)
- Deno Deploy
- Bun Runtime
- 10. Comparison with Fastify and Express
- Conclusion
- Related Posts
- Sources
1. Why Hono? The Competitive Edge
Hono means "flame" in Japanese, and the name reflects the framework's performance philosophy. The project, started by Yoshi Wada, had a core goal: a minimal framework with zero dependencies, running on Web Standards API (Request, Response, Headers), that works on every JavaScript runtime.
Key differences from competitors:
- Fastify: Node.js-only, ~250KB bundle, powerful but doesn't move to the edge
- Express: 30 years old, built on Node.js's http module rather than Web Standards
- Itty Router: very minimal, no middleware ecosystem
- Hono: built on Web Standards, multi-runtime, fully featured
In the Cloudflare Workers environment, Hono delivers roughly 150,000 req/sec throughput. That figure is 3-5x that of comparable frameworks. This is because Hono's router algorithms are specifically optimized for path matching and minimize the number of allocations.
2. Router Architecture: RegExpRouter, TrieRouter, SmartRouter
Hono's performance secret lies largely in its router choice. The framework offers three different routers:
RegExpRouter
Compiles all route patterns into a single giant regex. The routing operation has O(1) complexity because all patterns are tested at once. Its advantage grows as the number of routes increases.
1import { Hono } from 'hono';2import { RegExpRouter } from 'hono/router/reg-exp-router';3 4// Explicitly specifying RegExpRouter (SmartRouter already picks it)5const app = new Hono({ router: new RegExpRouter() });6 7app.get('/users/:id', (c) => {8 const id = c.req.param('id');9 return c.json({ userId: id });10});11 12app.get('/posts/:slug/comments/:commentId', (c) => {13 const { slug, commentId } = c.req.param();14 return c.json({ slug, commentId });15});16 17export default app;TrieRouter
Uses a trie (prefix tree) data structure. Can be faster than RegExpRouter when the number of dynamic segments is very high. Preferred for wildcard patterns.
SmartRouter (Default)
The default router since Hono 4.x. It analyzes the routes and automatically picks between RegExpRouter and TrieRouter. The developer doesn't need to think about the router at all.
1// SmartRouter is the default — nothing special to specify2const app = new Hono();3 4// Access all routes (for debugging):5// app.routes.forEach(r => console.log(r.method, r.path));3. Multi-Runtime Compatibility
Hono's most strategic feature is that the same code runs across multiple runtimes. This is achieved by sticking to the Web Standards API (Fetch API, Request, Response, Headers, URL, URLSearchParams).
Supported runtimes:
Runtime | Entry Point | Note |
|---|---|---|
Cloudflare Workers | export default app | Deployed with Wrangler |
Deno | Deno.serve(app.fetch) | Native HTTP |
Bun | Bun.serve({ fetch: app.fetch }) | Fast startup |
Node.js | serve(app) (@hono/node-server) | Adapter required |
Vercel Edge | export const GET = handle(app) | Edge runtime |
AWS Lambda | handle(app) (@hono/aws-lambda) | Adapter required |
Fastly Compute | app.fire() | WASM runtime |
1// src/index.ts — runtime-agnostic core2import { Hono } from 'hono';3import { cors } from 'hono/cors';4import { logger } from 'hono/logger';5import { prettyJSON } from 'hono/pretty-json';6 7type Env = {8 Variables: {9 userId: string;10 };11 Bindings: {12 DATABASE_URL: string;13 JWT_SECRET: string;14 };15};16 17const app = new Hono<Env>();18 19app.use('*', logger());20app.use('/api/*', cors({ origin: ['https://muhittincamdali.com'] }));21app.use('/api/*', prettyJSON());22 23app.get('/api/health', (c) => {24 return c.json({25 status: 'ok',26 runtime: c.env?.DATABASE_URL ? 'cf-workers' : 'local',27 timestamp: new Date().toISOString(),28 });29});30 31export default app;32 33// Cloudflare Workers: this file is enough (export default app)34// Deno: add Deno.serve(app.fetch)35// Bun: add Bun.serve({ fetch: app.fetch })36// Node: import { serve } from "@hono/node-server"; serve(app);4. Middleware Ecosystem
Hono's built-in middleware ships under the hono/ prefix. Third-party middleware lives under the @hono/ scope.
Key built-in middleware:
1import { Hono } from 'hono';2import { cors } from 'hono/cors';3import { compress } from 'hono/compress';4import { cache } from 'hono/cache';5import { bearerAuth } from 'hono/bearer-auth';6import { rateLimiter } from 'hono/rate-limiter';7import { secureHeaders } from 'hono/secure-headers';8import { timing } from 'hono/timing';9import { etag } from 'hono/etag';10 11const app = new Hono();12 13// Security headers (applied to all routes)14app.use('*', secureHeaders());15 16// Response timing (Server-Timing header)17app.use('*', timing());18 19// Gzip/Brotli compression20app.use('/api/*', compress());21 22// ETag cache control23app.use('/static/*', etag());24 25// CDN-level cache (Cloudflare Cache API)26app.use(27 '/api/public/*',28 cache({29 cacheName: 'hono-public-api',30 cacheControl: 'public, max-age=3600',31 })32);33 34// CORS — fine-grained setting35app.use(36 '/api/*',37 cors({38 origin: (origin) => {39 const allowed = ['https://muhittincamdali.com', 'https://app.muhittincamdali.com'];40 return allowed.includes(origin) ? origin : 'https://muhittincamdali.com';41 },42 allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],43 allowHeaders: ['Content-Type', 'Authorization'],44 credentials: true,45 maxAge: 86400,46 })47);48 49// Rate limiting (IP-based)50app.use(51 '/api/auth/*',52 rateLimiter({53 windowMs: 15 * 60 * 1000, // 15 minutes54 limit: 10,55 keyGenerator: (c) => c.req.header('CF-Connecting-IP') ?? 'unknown',56 handler: (c) => c.json({ error: 'Too many requests' }, 429),57 })58);59 60// Custom middleware — JWT verification61app.use('/api/protected/*', async (c, next) => {62 const token = c.req.header('Authorization')?.replace('Bearer ', '');63 if (!token) return c.json({ error: 'Unauthorized' }, 401);64 65 try {66 const { verify } = await import('hono/jwt');67 const payload = await verify(token, c.env.JWT_SECRET);68 c.set('userId', payload.sub as string);69 await next();70 } catch {71 return c.json({ error: 'Invalid token' }, 401);72 }73});74 75export default app;5. JSX Renderer and SSR
Hono can do server-side rendering with a built-in JSX renderer. React isn't required — Hono's own hono/jsx module converts TSX/JSX into HTML.
1/** @jsxImportSource hono/jsx */2import { Hono } from 'hono';3import { jsxRenderer } from 'hono/jsx-renderer';4 5const app = new Hono();6 7// Layout renderer8app.use(9 '*',10 jsxRenderer(({ children, title }) => {11 return (12 <html lang="tr">13 <head>14 <meta charset="UTF-8" />15 <meta name="viewport" content="width=device-width, initial-scale=1.0" />16 <title>{title ?? 'Hono App'}</title>17 <link rel="stylesheet" href="/static/styles.css" />18 </head>19 <body>20 <main>{children}</main>21 </body>22 </html>23 );24 })25);26 27// SSR page28app.get('/blog/:slug', async (c) => {29 const slug = c.req.param('slug');30 const post = await fetchBlogPost(slug);31 32 if (!post) return c.notFound();33 34 return c.render(35 <article>36 <h1>{post.title}</h1>37 <time dateTime={post.publishedAt}>{formatDate(post.publishedAt)}</time>38 <p>{post.summary}</p>39 </article>,40 { title: post.title }41 );42});43 44// Streaming SSR45app.get('/stream', (c) => {46 return c.streamText(async (stream) => {47 await stream.writeln('# Streaming Response');48 await stream.sleep(100);49 await stream.writeln('Data chunk 1...');50 await stream.sleep(100);51 await stream.writeln('Data chunk 2...');52 });53});54 55async function fetchBlogPost(slug: string) {56 // KV or D1 query57 return slug ? { title: 'Test Post', publishedAt: new Date().toISOString(), summary: 'Content...' } : null;58}59 60function formatDate(iso: string) {61 return new Date(iso).toLocaleDateString('tr-TR', {62 year: 'numeric', month: 'long', day: 'numeric',63 });64}65 66export default app;Pro Tip: In Hono JSX, all expressions are automatically escaped. If you need to render a string as raw HTML, first clean the content with a sanitizer library (e.g. sanitize-html), then use Hono's html tagged template literal. Never render untrusted input directly.6. RPC Mode: a Type-Safe Client
One of Hono's most powerful features is RPC mode. Type information for routes defined on the server side is automatically carried over to the client. This gives a developer experience similar to tRPC without requiring an extra schema definition.
1// server/routes/users.ts2import { Hono } from 'hono';3import { zValidator } from '@hono/zod-validator';4import { z } from 'zod';5 6const createUserSchema = z.object({7 name: z.string().min(2).max(100),8 email: z.string().email(),9 role: z.enum(['admin', 'user', 'editor']),10});11 12const usersRoute = new Hono()13 .get('/', async (c) => {14 const users = await getUsersFromDB();15 return c.json({ users, count: users.length });16 })17 .get('/:id', async (c) => {18 const id = c.req.param('id');19 const user = await getUserById(id);20 if (!user) return c.json({ error: 'User not found' }, 404);21 return c.json({ user });22 })23 .post('/', zValidator('json', createUserSchema), async (c) => {24 const data = c.req.valid('json');25 const user = await createUser(data);26 return c.json({ user }, 201);27 })28 .delete('/:id', async (c) => {29 await deleteUser(c.req.param('id'));30 return c.json({ success: true });31 });32 33export type UsersRouteType = typeof usersRoute;34export default usersRoute;35 36// server/index.ts37import { Hono } from 'hono';38import usersRoute from './routes/users';39 40const app = new Hono().route('/api/users', usersRoute);41 42export type AppType = typeof app;43export default app;44 45// client/api.ts — RPC client (browser or another service)46import { hc } from 'hono/client';47import type { AppType } from '../server/index';48 49const client = hc<AppType>('https://api.muhittincamdali.com');50 51// Fully type-safe calls — autocomplete works, type errors are caught at compile time52async function example() {53 // GET /api/users54 const listRes = await client.api.users.$get();55 const { users } = await listRes.json(); // users: User[] — inferred type56 57 // POST /api/users58 const createRes = await client.api.users.$post({59 json: {60 name: 'Muhittin Çamdali',61 email: '[email protected]',62 role: 'admin', // "admin" | "user" | "editor" — literal union63 },64 });65 66 const { user } = await createRes.json(); // user: User — full type67 68 // DELETE /api/users/:id69 await client.api.users[':id'].$delete({ param: { id: user.id } });70}71 72// Stub functions (real implementation lives in the DB layer)73async function getUsersFromDB() { return [] as Array<{ id: string; name: string; email: string; role: string }>; }74async function getUserById(_id: string) { return null; }75async function createUser(data: { name: string; email: string; role: string }) { return { id: crypto.randomUUID(), ...data }; }76async function deleteUser(_id: string) {}7. Validator and OpenAPI Integration
Besides Zod, Hono offers other validator options: @hono/valibot-validator, @hono/typebox-validator, @hono/arktype-validator. OpenAPI integration is provided via @hono/zod-openapi.
1import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';2import { swaggerUI } from '@hono/swagger-ui';3 4const app = new OpenAPIHono();5 6// Route schema — automatically generates the OpenAPI spec7const getUserRoute = createRoute({8 method: 'get',9 path: '/users/{id}',10 tags: ['Users'],11 summary: 'Get user',12 description: 'Returns a single user by ID',13 request: {14 params: z.object({15 }),16 },17 responses: {18 200: {19 content: {20 'application/json': {21 schema: z.object({22 user: z.object({23 name: z.string(),24 email: z.string().email(),25 createdAt: z.string().datetime(),26 }),27 }),28 },29 },30 description: 'User retrieved successfully',31 },32 404: {33 content: {34 'application/json': {35 schema: z.object({ error: z.string() }),36 },37 },38 description: 'User not found',39 },40 },41});42 43app.openapi(getUserRoute, async (c) => {44 const { id } = c.req.valid('param'); // id: string (uuid) — type-safe45 return c.json({46 user: {47 id,48 name: 'Test User',49 email: '[email protected]',50 createdAt: new Date().toISOString(),51 },52 });53});54 55// Swagger UI endpoint56app.get('/docs', swaggerUI({ url: '/openapi.json' }));57 58// OpenAPI JSON spec59app.doc('/openapi.json', {60 openapi: '3.0.0',61 info: {62 title: 'Portfolio API',63 version: '1.0.0',64 description: 'Muhittin Çamdali Portfolio API',65 },66 servers: [{ url: 'https://api.muhittincamdali.com', description: 'Production' }],67});68 69export default app;Pro Tip: When using@hono/zod-openapi, keep schema definitions in a separateschemas/folder and share the same schema for both route validation and OpenAPI doc generation. This approach eliminates code duplication and guarantees the docs always stay up to date.
8. Testing Strategies
Testing Hono applications is extremely easy because the framework runs on Web Standards. There's no need to spin up a Node.js HTTP server.
1// src/routes/posts.test.ts2import { describe, it, expect, beforeEach } from 'vitest';3import { Hono } from 'hono';4import postsRoute from './posts';5 6describe('Posts API', () => {7 let app: Hono;8 9 beforeEach(() => {10 app = new Hono().route('/api/posts', postsRoute);11 });12 13 it('GET /api/posts — returns the list', async () => {14 const res = await app.request('/api/posts');15 expect(res.status).toBe(200);16 17 const body = await res.json();18 expect(body).toHaveProperty('posts');19 expect(Array.isArray(body.posts)).toBe(true);20 });21 22 it('POST /api/posts — validation error', async () => {23 const res = await app.request('/api/posts', {24 method: 'POST',25 headers: { 'Content-Type': 'application/json' },26 body: JSON.stringify({ title: '' }), // Invalid — min length 127 });28 29 expect(res.status).toBe(400);30 const body = await res.json();31 expect(body).toHaveProperty('error');32 });33 34 it('POST /api/posts — successful creation', async () => {35 const res = await app.request('/api/posts', {36 method: 'POST',37 headers: {38 'Content-Type': 'application/json',39 Authorization: 'Bearer valid-test-token',40 },41 body: JSON.stringify({42 title: 'Test Post',43 content: 'Test content...',44 published: false,45 }),46 });47 48 expect(res.status).toBe(201);49 const { post } = await res.json();50 expect(post.title).toBe('Test Post');51 expect(post.id).toBeDefined();52 });53 54 it('Middleware — 401 without JWT', async () => {55 const res = await app.request('/api/posts', { method: 'POST' });56 expect(res.status).toBe(401);57 });58 59 it('Rate limiting — 429 after threshold', async () => {60 // Send 10 requests, the 11th should return 42961 for (let i = 0; i < 10; i++) {62 await app.request('/api/auth/login', {63 method: 'POST',64 headers: {65 'Content-Type': 'application/json',66 'CF-Connecting-IP': '1.2.3.4',67 },68 body: JSON.stringify({ email: '[email protected]', password: 'wrong' }),69 });70 }71 72 const res = await app.request('/api/auth/login', {73 method: 'POST',74 headers: {75 'Content-Type': 'application/json',76 'CF-Connecting-IP': '1.2.3.4',77 },78 body: JSON.stringify({ email: '[email protected]', password: 'wrong' }),79 });80 81 expect(res.status).toBe(429);82 });83});9. Production Deployment Patterns
Cloudflare Workers (Wrangler)
1// wrangler.toml — basic configuration2// name = "portfolio-api"3// main = "src/index.ts"4// compatibility_date = "2026-04-01"5// compatibility_flags = ["nodejs_compat"]6//7// [vars]8// ENVIRONMENT = "production"9//10// [[kv_namespaces]]11// binding = "CACHE_KV"12// id = "xxx"13//14// [[d1_databases]]15// binding = "DB"16// database_name = "portfolio-db"17// database_id = "yyy"18 19// src/index.ts — Cloudflare Workers entry point20import { Hono } from 'hono';21 22interface CloudflareEnv {23 CACHE_KV: KVNamespace;24 DB: D1Database;25 JWT_SECRET: string;26 ENVIRONMENT: string;27}28 29const app = new Hono<{ Bindings: CloudflareEnv }>();30 31app.get('/api/status', (c) => {32 return c.json({33 env: c.env.ENVIRONMENT,34 timestamp: new Date().toISOString(),35 });36});37 38// D1 database query39app.get('/api/posts/:id', async (c) => {40 const id = c.req.param('id');41 const result = await c.env.DB42 .prepare('SELECT id, title, slug, published_at FROM posts WHERE id = ? AND published = 1')43 .bind(id)44 .first();45 46 if (!result) return c.json({ error: 'Not found' }, 404);47 return c.json({ post: result });48});49 50// KV cache pattern51app.get('/api/config', async (c) => {52 const cached = await c.env.CACHE_KV.get('site-config', 'json');53 if (cached) return c.json(cached);54 55 const config = { theme: 'dark', version: '2.0.0' };56 await c.env.CACHE_KV.put('site-config', JSON.stringify(config), { expirationTtl: 3600 });57 return c.json(config);58});59 60export default app;61// Deploy: wrangler deploy62// Staging: wrangler deploy --env stagingDeno Deploy
1// main.ts — Deno Deploy entry point2import { Hono } from 'npm:hono';3import { cors } from 'npm:hono/cors';4import { logger } from 'npm:hono/logger';5 6const app = new Hono();7 8app.use('*', logger());9app.use('/api/*', cors());10 11app.get('/', (c) => c.text('Hono on Deno Deploy!'));12app.get('/api/ping', (c) => c.json({ pong: true, runtime: 'deno', ts: Date.now() }));13 14Deno.serve(app.fetch);15// Deploy: deno deploy --project=my-project main.tsBun Runtime
1// server.ts — Bun runtime entry point2import { Hono } from 'hono';3 4const app = new Hono();5app.get('/', (c) => c.text('Hono on Bun!'));6app.get('/api/ping', (c) => c.json({ pong: true, runtime: 'bun' }));7 8const server = Bun.serve({9 fetch: app.fetch,10 port: Number(process.env.PORT) || 3000,11 development: process.env.NODE_ENV !== 'production',12});13 14console.log(`Bun server running on port ${server.port}`);15// Run: bun run server.ts16// Compile: bun build server.ts --compile --outfile server10. Comparison with Fastify and Express
Feature | Hono | Fastify | Express |
|---|---|---|---|
Bundle size | ~12KB | ~250KB | ~200KB |
Runtime | Multi (Workers, Deno, Bun, Node) | Node.js only | Node.js only |
Req/sec (CF Workers) | ~150K | N/A | N/A |
Req/sec (Node.js) | ~80K | ~75K | ~35K |
TypeScript | Native, zero-config | Plugin required | @types required |
Web Standards | Fully compliant | Partial | No |
RPC mode | Yes (hono/client) | No | No |
OpenAPI | @hono/zod-openapi | @fastify/swagger | express-openapi |
Edge deployment | Primary target | Hard | Very hard |
Ecosystem maturity | Growing (~2022) | Mature (~2016) | Very mature (~2010) |
When to choose Hono:
- If you're targeting Cloudflare Workers, Deno Deploy, Bun, or Vercel Edge
- If you're following a multi-runtime strategy
- If you want a type-safe fullstack via RPC mode while avoiding tRPC's complexity
- If bundle size is critical
When to choose Fastify:
- A Node.js monolith with no edge migration plan
- If you're actively using the Fastify plugin ecosystem (PostgreSQL, Redis, auth)
- If the team's experience is on the Express/Fastify side
GOLDEN TIP
The most valuable insight in this article
This tip holds the article's most important takeaway.
Easter Egg
You found a hidden gem!
There's a hidden detail in this section. Want to uncover it?
Reader Reward
File-based routing support is active as of Hono 4.4+. The folder structure automatically becomes routes:
src/routes/ index.ts → GET / api/ users/ index.ts → GET /api/users, POST /api/users [id].ts → GET /api/users/:id, PUT /api/users/:id posts/ index.ts → GET /api/posts [slug]/ comments.ts → GET /api/posts/:slug/comments// src/app.ts — dynamic routing via glob import (Bun / Vite)import { Hono } from 'hono'; const app = new Hono(); // Glob import via Bun or the Vite build toolconst routeModules = import.meta.glob('./routes/**/*.ts', { eager: true }); for (const [filePath, module] of Object.entries(routeModules)) { const routePath = filePath .replace('./routes', '') .replace('/index.ts', '') .replace('.ts', '') .replace(/\[([^\]]+)\]/g, ':$1'); // [id] → :id conversion const mod = module as { default?: Hono }; if (mod.default instanceof Hono) { app.route(routePath || '/', mod.default); }} export default app;Conclusion
As of 2026, Hono.js has effectively become the standard of the serverless and edge computing world. A sub-12KB bundle size, ~150K req/sec throughput, native TypeScript support, multi-runtime compatibility, and a tRPC-like RPC mode make Hono ideal both for greenfield projects and for existing applications migrating to Cloudflare Workers.
Zero-config high performance via SmartRouter, automatic API documentation via @hono/zod-openapi, and type-safe frontend integration via hono/client — this trio makes Hono one of the frameworks offering the most efficient backend development experience of 2026.
Related Posts
- Firebase Data Connect: GraphQL ve Cloud SQL Entegrasyonu (in Turkish)
- The Server-Side Swift Ecosystem: Vapor, Hummingbird, and Swift on Server
- RAG vs Fine-Tuning: Production LLM Rehberi (in Turkish)
- Claude Computer Use API: Production Rehberi (in Turkish)
- Drizzle ORM + Turso: Edge SQLite Database Pattern 2026 Production
Sources
- Hono.js Official Docs — Official documentation, all adapters
- Hono GitHub — Source code, RFCs, migration guides
- Cloudflare Workers Docs — Workers runtime reference
- Hono Examples Repository — Real-world examples
- Bun HTTP Server Docs — Bun runtime integration
Tags
iOS Development News
Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.
We respect your privacy. You can unsubscribe at any time.

