Hono.js Complete Guide 2026: Ultra-Fast Edge Web Framework
Complete guide to Hono.js in 2026. Routing, middleware, TypeScript, deployment to Cloudflare Workers, Deno, and Bun. Build blazing-fast edge APIs with real code examples.
Hono.js has emerged as one of the most compelling web frameworks of the past two years. Originally designed for Cloudflare Workers, it has grown into a universal edge-first framework that runs on Bun, Deno, Node.js, Fastly, and AWS Lambda β all from a single codebase. If you have not looked at Hono seriously yet, 2026 is the year to start.
This guide walks through everything you need to know: the architecture that makes Hono fast, routing and middleware, TypeScript integration, JSX support, RPC mode, and step-by-step deployment to the three most popular runtimes.
What Is Hono.js
Hono (η, meaning βflameβ in Japanese) is a small, fast, and multi-runtime web framework built on Web Standard APIs β specifically the Fetch API and the Request/Response primitives. Because it relies only on APIs that exist natively in every modern JavaScript runtime, Hono code needs almost no adaptation to run in different environments.
The key design decisions that define Hono:
- Zero dependencies β the core package ships no third-party runtime dependencies.
- Tiny bundle size β the core is around 14 kB minified. This matters enormously on edge platforms where cold starts are measured in milliseconds.
- Web Standards first β every handler receives a standard
Requestand returns a standardResponse. No proprietaryreq/resobjects to learn. - TypeScript native β the entire framework is written in TypeScript and ships full type definitions, including route-level type inference.
Why Hono Is Fast
Speed in web frameworks comes from several places: routing algorithm efficiency, middleware overhead, serialization cost, and runtime startup time.
Hono uses a Trie-based RegExpRouter as its default router. Route matching is O(log n) in the average case and avoids the linear scan that older frameworks perform. For applications with many routes, this difference compounds quickly.
The framework avoids class instantiation overhead by leaning on closures and plain functions wherever possible. Middleware is composed as a flat async chain rather than a deeply nested callback tree, which reduces call stack depth and makes stack traces readable.
On edge runtimes like Cloudflare Workers, the cold start advantage is decisive. A Hono application initializes in under 1 ms. An equivalent Express application running on Node.js in a Lambda function can take 300-800 ms to cold start. For user-facing APIs, that gap is the difference between a fast product and a slow one.
Installation and Basic Setup
Installing Hono depends on your runtime. For Node.js and Bun, use npm:
npm install hono
# or
bun add hono
For Cloudflare Workers with Wrangler:
npm create cloudflare@latest my-api -- --template hono
A minimal Hono application looks like this:
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello from Hono'))
export default app
The c parameter is a Context object. It wraps the incoming Request and provides helper methods for building responses. You will use c.req, c.res, c.json(), c.text(), c.html(), and c.redirect() constantly.
Routing API
Honoβs routing API is clean and consistent. Every HTTP method has a matching method on the app instance:
import { Hono } from 'hono'
const app = new Hono()
// Basic methods
app.get('/users', (c) => c.json({ users: [] }))
app.post('/users', async (c) => {
const body = await c.req.json()
return c.json({ created: body }, 201)
})
app.put('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ updated: id })
})
app.delete('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ deleted: id })
})
// Wildcard route
app.get('/files/*', (c) => {
const path = c.req.param('*')
return c.text(`Serving: ${path}`)
})
// Match any method
app.all('/health', (c) => c.json({ status: 'ok' }))
export default app
Route Groups
Hono supports route grouping, which is essential for organizing larger APIs:
import { Hono } from 'hono'
const app = new Hono()
// Create a sub-application for /api/v1
const v1 = new Hono()
v1.get('/products', (c) => c.json({ products: [] }))
v1.get('/products/:id', (c) => c.json({ id: c.req.param('id') }))
v1.post('/products', async (c) => {
const data = await c.req.json()
return c.json({ created: data }, 201)
})
// Mount at prefix
app.route('/api/v1', v1)
export default app
This pattern scales cleanly. Each sub-application is independently testable and can define its own middleware chain.
Middleware System
Middleware in Hono follows the same signature as route handlers β a function that receives a Context and a next function. Calling await next() passes control to the next handler in the chain.
import { Hono } from 'hono'
const app = new Hono()
// Global middleware β runs on every request
app.use('*', async (c, next) => {
console.log(`${c.req.method} ${c.req.url}`)
const start = Date.now()
await next()
const elapsed = Date.now() - start
c.res.headers.set('X-Response-Time', `${elapsed}ms`)
})
// Route-specific middleware
const requireAuth = async (c: any, next: any) => {
const token = c.req.header('Authorization')
if (!token || !token.startsWith('Bearer ')) {
return c.json({ error: 'Unauthorized' }, 401)
}
await next()
}
app.get('/protected', requireAuth, (c) => c.json({ secret: 'data' }))
export default app
Built-in Middleware
Hono ships a solid set of built-in middleware packages. They are opt-in via separate imports, so you only pay the bundle cost for what you use:
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { prettyJSON } from 'hono/pretty-json'
import { compress } from 'hono/compress'
import { bearerAuth } from 'hono/bearer-auth'
import { rateLimiter } from 'hono/rate-limiter'
const app = new Hono()
app.use(logger())
app.use(compress())
app.use(prettyJSON())
app.use('/api/*', cors({
origin: ['https://myapp.com', 'https://staging.myapp.com'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
}))
app.use('/api/*', bearerAuth({ token: process.env.API_SECRET! }))
export default app
TypeScript Integration
Honoβs TypeScript support goes well beyond basic type definitions. It provides route-level type inference, which means you can define the expected shape of route parameters, query strings, and JSON bodies, and the framework enforces them at compile time.
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
// Define typed environment for bindings (Cloudflare Workers)
type Env = {
Bindings: {
DATABASE_URL: string
API_KEY: string
}
Variables: {
userId: string
}
}
const app = new Hono<Env>()
// Zod validation with automatic type inference
const createUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(['admin', 'user', 'viewer']),
})
app.post(
'/users',
zValidator('json', createUserSchema),
async (c) => {
// data is fully typed as { name: string; email: string; role: 'admin' | 'user' | 'viewer' }
const data = c.req.valid('json')
// Access typed environment bindings
const dbUrl = c.env.DATABASE_URL
return c.json({ created: data }, 201)
}
)
// Query parameter validation
const listUsersSchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
role: z.enum(['admin', 'user', 'viewer']).optional(),
})
app.get('/users', zValidator('query', listUsersSchema), (c) => {
const { page, limit, role } = c.req.valid('query')
return c.json({ page, limit, role, users: [] })
})
export default app
The @hono/zod-validator adapter is the standard approach for runtime validation. It integrates with Zodβs inference so validated data is immediately typed without additional type assertions.
JSX Support
Hono supports JSX for server-side rendering. You do not need React β Hono ships its own JSX runtime that produces HTML strings with zero client-side JavaScript overhead:
// index.tsx
import { Hono } from 'hono'
import { html } from 'hono/html'
const app = new Hono()
// Using JSX
const Layout = ({ children, title }: { children: any; title: string }) => (
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>{title}</title>
</head>
<body>{children}</body>
</html>
)
const ProductCard = ({ name, price }: { name: string; price: number }) => (
<div class="card">
<h2>{name}</h2>
<p>${price.toFixed(2)}</p>
</div>
)
app.get('/products', (c) => {
const products = [
{ name: 'Widget Pro', price: 29.99 },
{ name: 'Gadget Plus', price: 49.99 },
]
return c.html(
<Layout title="Products">
<h1>Our Products</h1>
{products.map((p) => (
<ProductCard name={p.name} price={p.price} />
))}
</Layout>
)
})
export default app
Set "jsx": "react-jsx" and "jsxImportSource": "hono/jsx" in your tsconfig.json to enable JSX compilation.
RPC Mode
RPC mode is one of Honoβs most powerful and distinctive features. It generates a fully type-safe client from your server route definitions β no code generation step required, no schema files to maintain.
// server.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
.get('/posts', (c) => {
return c.json({ posts: [{ id: 1, title: 'Hello Hono' }] })
})
.post(
'/posts',
zValidator('json', z.object({ title: z.string(), body: z.string() })),
async (c) => {
const { title, body } = c.req.valid('json')
return c.json({ id: 2, title, body }, 201)
}
)
.get('/posts/:id', (c) => {
const id = Number(c.req.param('id'))
return c.json({ id, title: 'Post title' })
})
export type AppType = typeof app
export default app
// client.ts
import { hc } from 'hono/client'
import type { AppType } from './server'
// The client is fully typed β autocomplete works on routes, params, and responses
const client = hc<AppType>('https://api.example.com')
// Fetch all posts β response type is inferred automatically
const res = await client.posts.$get()
const { posts } = await res.json()
// Create a post β TypeScript enforces the request body shape
const createRes = await client.posts.$post({
json: { title: 'New Post', body: 'Content here' },
})
const created = await createRes.json()
// Route parameters are type-checked
const postRes = await client.posts[':id'].$get({ param: { id: '1' } })
const post = await postRes.json()
RPC mode eliminates an entire category of client-server type drift bugs. If you change a routeβs response shape on the server, TypeScript immediately flags every client call that assumes the old shape.
Practical REST API Tutorial
Here is a complete REST API for a bookmark manager, demonstrating all the patterns covered so far:
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
// In-memory store for demonstration
const bookmarks: { id: number; url: string; title: string; tags: string[] }[] = []
let nextId = 1
const bookmarkSchema = z.object({
url: z.string().url(),
title: z.string().min(1).max(200),
tags: z.array(z.string()).default([]),
})
const app = new Hono()
app.use(logger())
app.use('*', cors())
// List bookmarks with optional tag filter
app.get('/bookmarks', (c) => {
const tag = c.req.query('tag')
const results = tag
? bookmarks.filter((b) => b.tags.includes(tag))
: bookmarks
return c.json({ bookmarks: results, total: results.length })
})
// Get single bookmark
app.get('/bookmarks/:id', (c) => {
const id = Number(c.req.param('id'))
const bookmark = bookmarks.find((b) => b.id === id)
if (!bookmark) {
return c.json({ error: 'Bookmark not found' }, 404)
}
return c.json(bookmark)
})
// Create bookmark
app.post('/bookmarks', zValidator('json', bookmarkSchema), (c) => {
const data = c.req.valid('json')
const bookmark = { id: nextId++, ...data }
bookmarks.push(bookmark)
return c.json(bookmark, 201)
})
// Update bookmark
app.put(
'/bookmarks/:id',
zValidator('json', bookmarkSchema.partial()),
(c) => {
const id = Number(c.req.param('id'))
const index = bookmarks.findIndex((b) => b.id === id)
if (index === -1) {
return c.json({ error: 'Bookmark not found' }, 404)
}
const updates = c.req.valid('json')
bookmarks[index] = { ...bookmarks[index], ...updates }
return c.json(bookmarks[index])
}
)
// Delete bookmark
app.delete('/bookmarks/:id', (c) => {
const id = Number(c.req.param('id'))
const index = bookmarks.findIndex((b) => b.id === id)
if (index === -1) {
return c.json({ error: 'Bookmark not found' }, 404)
}
bookmarks.splice(index, 1)
return c.body(null, 204)
})
export default app
Deployment Targets
Cloudflare Workers
Cloudflare Workers is Honoβs home runtime. Deploy with Wrangler:
# wrangler.toml
name = "my-api"
main = "src/index.ts"
compatibility_date = "2026-01-01"
[vars]
ENVIRONMENT = "production"
// src/index.ts
import { Hono } from 'hono'
type Bindings = {
KV: KVNamespace
DB: D1Database
ENVIRONMENT: string
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/kv/:key', async (c) => {
const key = c.req.param('key')
const value = await c.env.KV.get(key)
if (!value) return c.json({ error: 'Not found' }, 404)
return c.json({ key, value })
})
export default app
wrangler deploy
Deno Deploy
// main.ts
import { Hono } from 'npm:hono'
import { serve } from 'https://deno.land/std/http/server.ts'
const app = new Hono()
app.get('/', (c) => c.json({ runtime: 'Deno', version: Deno.version.deno }))
app.get('/env/:key', (c) => {
const key = c.req.param('key')
const value = Deno.env.get(key) ?? null
return c.json({ key, value })
})
serve(app.fetch)
Deploy with the Deno CLI:
deployctl deploy --project=my-api main.ts
Bun Server
// server.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.json({ runtime: 'Bun', version: Bun.version }))
app.get('/file/:name', async (c) => {
const name = c.req.param('name')
const file = Bun.file(`./public/${name}`)
const exists = await file.exists()
if (!exists) return c.json({ error: 'File not found' }, 404)
return new Response(file)
})
export default {
port: 3000,
fetch: app.fetch,
}
bun run server.ts
Hono vs Express vs Fastify
Choosing a framework depends on your context. Here is an honest comparison across the dimensions that matter most in 2026:
| Criterion | Hono | Express | Fastify |
|---|---|---|---|
| Runtime support | Edge, Bun, Deno, Node | Node only | Node, Bun |
| Bundle size | ~14 kB | ~200 kB | ~120 kB |
| Requests/sec (benchmark) | ~500k+ | ~80k | ~350k |
| TypeScript | First-class, built-in | External types only | Good, improving |
| Ecosystem maturity | Growing fast | Massive, 15+ years | Large, stable |
| Middleware compatibility | Hono-specific | Massive npm ecosystem | Fastify plugin system |
| RPC / type-safe client | Yes (built-in) | No | Via external libs |
| JSX / SSR | Yes (built-in) | No | No |
| Learning curve | Low | Very low | Medium |
Use Hono when you are targeting edge runtimes, need a type-safe client-server contract, want multi-runtime portability, or are building a new project from scratch.
Use Express when you need maximum npm middleware compatibility, are extending an existing Express codebase, or have team members already familiar with it. Express is not fast, but it is predictable and well-documented.
Use Fastify when you are running on Node.js and need higher throughput than Express without migrating to edge runtimes. Fastifyβs schema-based validation with JSON Schema is excellent for JSON-heavy APIs.
The honest summary: for new projects in 2026, Hono is the default choice unless you have a specific reason to reach for one of the others. Its performance advantage on edge runtimes is substantial, and the TypeScript integration β especially RPC mode β eliminates real categories of bugs.
Testing Hono Applications
Honoβs adherence to Web Standards makes testing straightforward. The app.request() method accepts a URL string or a full Request object and returns a Response β no HTTP server needed:
import { describe, it, expect } from 'vitest'
import app from './server'
describe('Bookmarks API', () => {
it('returns empty list initially', async () => {
const res = await app.request('/bookmarks')
expect(res.status).toBe(200)
const body = await res.json()
expect(body.bookmarks).toEqual([])
})
it('creates a bookmark', async () => {
const res = await app.request('/bookmarks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: 'https://hono.dev',
title: 'Hono Documentation',
tags: ['framework', 'edge'],
}),
})
expect(res.status).toBe(201)
const body = await res.json()
expect(body.url).toBe('https://hono.dev')
expect(body.id).toBeDefined()
})
it('rejects invalid URLs', async () => {
const res = await app.request('/bookmarks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: 'not-a-url', title: 'Bad' }),
})
expect(res.status).toBe(400)
})
})
Run tests with Vitest:
npx vitest run
No test server setup, no port conflicts, no async teardown issues. Tests are fast because they never touch the network stack.
Error Handling
Hono provides a global error handler and a not-found handler:
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
const app = new Hono()
// Custom not-found response
app.notFound((c) => {
return c.json({ error: `Route ${c.req.path} not found` }, 404)
})
// Global error handler
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status)
}
console.error('Unhandled error:', err)
return c.json({ error: 'Internal server error' }, 500)
})
// Throw HTTPException in any handler
app.get('/admin', (c) => {
const role = c.req.header('X-User-Role')
if (role !== 'admin') {
throw new HTTPException(403, { message: 'Admin access required' })
}
return c.json({ admin: true })
})
export default app
HTTPException carries a status code and optional message. The global error handler catches it and formats a consistent response. This pattern keeps error handling out of individual route handlers while giving you full control over response format.
What to Build With Hono in 2026
Hono is a strong fit for several categories of projects:
Edge API gateways β authentication, rate limiting, request routing, and response caching at the CDN edge, before traffic reaches your origin.
Backend for Frontend (BFF) layers β aggregate multiple microservice calls into optimized responses for specific frontend views, running at the edge for low latency.
Full-stack Cloudflare applications β combine Hono with Cloudflare Workers, D1 (SQLite), KV, R2 (object storage), and Queues to build complete applications with no traditional server.
Type-safe internal APIs β use RPC mode to build internal services where the TypeScript types flow automatically from server to client, eliminating manual API documentation for team-internal services.
Server-side rendering β use Honoβs JSX runtime for lightweight SSR without shipping a React bundle to the client. Combine with htmx for dynamic interactions.
Summary
Hono.js has earned its place as the leading edge web framework in 2026. Its advantages β tiny footprint, Web Standards foundation, first-class TypeScript, RPC mode, and multi-runtime support β address real problems that developers face when building modern APIs and web services.
The framework is mature enough for production use (major companies run it in production today) while still being small enough to understand completely. The source code is readable, the documentation is thorough, and the community is active.
If you are starting a new API project today, the practical choice is to start with Hono, deploy to Cloudflare Workers, and use RPC mode for any TypeScript clients. That combination gives you excellent performance, strong type safety, and low operational complexity.
50 CLI Commands That Save Hours Every Week
The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.
- Git mastery (10 cmds)
- File & search wizardry
- Docker & DevOps tricks
- Process & network tools
- Productivity boosters
- macOS & Linux install guide
No spam. 1 email/week max. Unsubscribe anytime.
Found this guide useful? Check out our free developer tools.
Affiliate disclosure: Some links below are affiliate links β we may earn a small commission at no extra cost to you. Learn more.
Recommended Tools & Resources
DigitalOcean
$200 credit for new users. Simple, affordable cloud hosting for developers.
GitHub Student Pack
Free access to 100+ developer tools. Perfect for students and new devs.
Vercel
Deploy frontend apps instantly. Free tier is generous for side projects.
DevPlaybook Products
Boilerplates, scripts & AI toolkits to 10x your dev workflow.