Hono.js Full Stack Guide 2026: Ultra-Fast Web Framework for Every Runtime
Master Hono.js in 2026 — middleware patterns, RPC client, JSX SSR, and deployment to Cloudflare Workers, Deno Deploy, and Bun. Complete full-stack guide with code examples.
Hono.js Full Stack Guide 2026: Ultra-Fast Web Framework for Every Runtime
The web framework landscape has fragmented. You’re no longer building only for Node.js — your code might run on Cloudflare Workers, Deno Deploy, Bun, or AWS Lambda Edge, often across the same project. Hono.js was built for exactly this world.
Hono (炎, Japanese for “flame”) is a small, fast, edge-compatible web framework with zero dependencies, a TypeScript-first API, and a runtime-agnostic design that lets you deploy the same codebase anywhere JavaScript runs. In 2026, it’s the go-to choice for teams building APIs, edge functions, and full-stack apps that need to move fast without runtime lock-in.
This guide covers everything: routing, middleware, the RPC client, JSX SSR, and production deployment patterns across the major runtimes.
Why Hono in 2026?
The numbers tell the story. In the TechEmpower benchmarks, Hono consistently outperforms Express by 5–10x on JSON serialization workloads. Its bundle size sits around 14KB with zero dependencies. But raw speed is only part of the story.
| Framework | Bundle Size | Runtimes | TypeScript | RPC Client |
|---|---|---|---|---|
| Hono | ~14KB | All major | Native | Yes |
| Express | ~200KB+ | Node only | Via types | No |
| Fastify | ~300KB | Node/Bun | Via plugin | No |
| Elysia | ~50KB | Bun | Native | Yes |
Hono’s real advantage is uniformity. You write one application and deploy it to whichever runtime fits your infrastructure. No adapter rewrites, no platform-specific abstractions to fight.
Installation and Quick Start
# Node.js
npm create hono@latest my-app
cd my-app && npm install
# Bun
bun create hono my-app
cd my-app && bun install
# Deno
deno run npm:create-hono@latest my-app
The CLI scaffolds runtime-specific entry points while keeping your application code identical. Here’s the simplest possible Hono app:
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export default app
The c context object is the heart of Hono. It provides access to the request, response helpers, environment variables, and any values you attach during middleware execution.
Routing
Hono’s router is its fastest component — built on a RegExp-based router (RegExpRouter) for simple cases and a more sophisticated linear router (SmartRouter) for complex applications. You get all standard HTTP methods plus convenient patterns:
import { Hono } from 'hono'
const app = new Hono()
// Static routes
app.get('/health', (c) => c.json({ status: 'ok' }))
// Named parameters
app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ id })
})
// Wildcard
app.get('/files/*', (c) => {
const path = c.req.param('*')
return c.text(`Serving: ${path}`)
})
// Query parameters
app.get('/search', (c) => {
const q = c.req.query('q') ?? ''
const page = Number(c.req.query('page') ?? 1)
return c.json({ q, page })
})
// Multiple methods
app.on(['GET', 'POST'], '/items', (c) => {
return c.json({ method: c.req.method })
})
export default app
Route Groups and Nesting
For larger applications, group related routes using app.route():
import { Hono } from 'hono'
const app = new Hono()
const api = new Hono().basePath('/api')
const v1 = new Hono()
v1.get('/users', (c) => c.json({ users: [] }))
v1.post('/users', async (c) => {
const body = await c.req.json()
return c.json({ created: body }, 201)
})
v1.get('/posts', (c) => c.json({ posts: [] }))
api.route('/v1', v1)
app.route('/', api)
export default app
This produces clean URLs: GET /api/v1/users, POST /api/v1/users, GET /api/v1/posts.
Middleware
Hono’s middleware system is composable and explicit. Middleware runs in the order it’s registered, and you can scope it to specific routes or apply it globally.
Built-in Middleware
Hono ships a comprehensive set of middleware out of the box:
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { jwt } from 'hono/jwt'
import { compress } from 'hono/compress'
import { etag } from 'hono/etag'
import { secureHeaders } from 'hono/secure-headers'
import { rateLimiter } from 'hono/rate-limiter'
const app = new Hono()
// Global middleware
app.use('*', logger())
app.use('*', compress())
app.use('*', etag())
app.use('*', secureHeaders())
// CORS with configuration
app.use('/api/*', cors({
origin: ['https://app.example.com'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
}))
// JWT protection on specific routes
app.use('/api/protected/*', jwt({
secret: 'your-secret-key',
}))
// Rate limiting
app.use('/api/*', rateLimiter({
windowMs: 15 * 60 * 1000,
limit: 100,
}))
export default app
Custom Middleware
Writing your own middleware follows the same pattern:
import { Hono, MiddlewareHandler } from 'hono'
// Timing middleware
const timing: MiddlewareHandler = async (c, next) => {
const start = Date.now()
await next()
const elapsed = Date.now() - start
c.res.headers.set('X-Response-Time', `${elapsed}ms`)
}
// Context injection middleware
const requestId: MiddlewareHandler = async (c, next) => {
const id = crypto.randomUUID()
c.set('requestId', id)
c.res.headers.set('X-Request-Id', id)
await next()
}
// Auth middleware with typed context
type Variables = {
user: { id: string; role: string }
}
const authMiddleware: MiddlewareHandler<{ Variables: Variables }> = async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
if (!token) return c.json({ error: 'Unauthorized' }, 401)
// Verify token and attach user
const user = await verifyToken(token)
c.set('user', user)
await next()
}
const app = new Hono<{ Variables: Variables }>()
app.use('*', timing)
app.use('*', requestId)
app.use('/protected/*', authMiddleware)
app.get('/protected/profile', (c) => {
const user = c.get('user') // Fully typed
return c.json({ user })
})
The RPC Client: Type-Safe API Calls
One of Hono’s most powerful features is the RPC client — it generates a fully typed client from your route definitions, eliminating the need for manual API client code or a separate GraphQL/tRPC layer.
Setting Up RPC
// server/routes/users.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
})
const users = new Hono()
.get('/', (c) => {
return c.json({ users: [{ id: '1', name: 'Alice' }] })
})
.post('/', zValidator('json', createUserSchema), async (c) => {
const data = c.req.valid('json') // Fully typed
const user = await createUser(data)
return c.json({ user }, 201)
})
.get('/:id', (c) => {
const id = c.req.param('id')
return c.json({ user: { id, name: 'Alice' } })
})
export default users
export type UsersRoute = typeof users
// server/index.ts
import { Hono } from 'hono'
import users from './routes/users'
const app = new Hono().route('/users', users)
export default app
export type AppType = typeof app
Using the Client
// client/api.ts
import { hc } from 'hono/client'
import type { AppType } from '../server'
const client = hc<AppType>('http://localhost:3000')
// Fully typed — your IDE knows the shape of every request and response
const res = await client.users.$get()
const data = await res.json()
// data.users is typed as { id: string; name: string }[]
const newUser = await client.users.$post({
json: { name: 'Bob', email: '[email protected]' },
})
const created = await newUser.json()
// created.user is fully typed
The RPC client works across all runtimes and integrates seamlessly with React, Svelte, or any frontend framework. Use the API Request Builder tool to prototype API calls before wiring up the client.
JSX Server-Side Rendering
Hono supports JSX out of the box for server-side rendering without React. This is useful for building lightweight server-rendered UIs, email templates, or documentation pages.
// tsconfig.json: add "jsxImportSource": "hono/jsx"
import { Hono } from 'hono'
import { html } from 'hono/html'
const app = new Hono()
// Simple HTML template
app.get('/', (c) => {
return c.html(
<html>
<head>
<title>Hono App</title>
</head>
<body>
<h1>Hello from Hono JSX!</h1>
</body>
</html>
)
})
// With Streaming
import { streamSSE } from 'hono/streaming'
app.get('/stream', (c) => {
return streamSSE(c, async (stream) => {
let count = 0
const interval = setInterval(async () => {
await stream.writeSSE({
data: `Message ${count}`,
event: 'message',
id: String(count),
})
count++
if (count >= 10) {
clearInterval(interval)
stream.close()
}
}, 1000)
})
})
export default app
Deployment Guide
Cloudflare Workers
Cloudflare Workers is the most popular deployment target for Hono. Your code runs at the edge in 300+ locations globally.
// src/index.ts
import { Hono } from 'hono'
type Bindings = {
MY_KV: KVNamespace
DB: D1Database
MY_BUCKET: R2Bucket
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/kv/:key', async (c) => {
const value = await c.env.MY_KV.get(c.req.param('key'))
return c.json({ value })
})
app.get('/db/users', async (c) => {
const stmt = c.env.DB.prepare('SELECT * FROM users LIMIT 10')
const result = await stmt.all()
return c.json({ users: result.results })
})
export default app
# wrangler.toml
name = "my-hono-app"
main = "src/index.ts"
compatibility_date = "2026-01-01"
[[kv_namespaces]]
binding = "MY_KV"
id = "your-kv-namespace-id"
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "your-db-id"
npx wrangler deploy
Use the Cloudflare Workers KV Simulator to test KV operations locally before deploying.
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.text('Running on Deno!'))
Deno.serve(app.fetch)
Deploy with deployctl deploy --project=my-app main.ts.
Bun
// index.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Running on Bun!'))
export default {
port: 3000,
fetch: app.fetch,
}
bun run index.ts
Bun’s HTTP server is currently the fastest JavaScript runtime for Hono, achieving over 200,000 requests per second on typical hardware.
Node.js with Adapter
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
const app = new Hono()
app.get('/', (c) => c.text('Running on Node!'))
serve({ fetch: app.fetch, port: 3000 })
Validation with Zod
Input validation is critical for any production API. Hono integrates cleanly with Zod via the @hono/zod-validator package:
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
const querySchema = z.object({
page: z.coerce.number().min(1).default(1),
limit: z.coerce.number().min(1).max(100).default(20),
})
const bodySchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(10),
tags: z.array(z.string()).max(5).optional(),
})
app.get('/posts', zValidator('query', querySchema), (c) => {
const { page, limit } = c.req.valid('query')
return c.json({ page, limit })
})
app.post('/posts', zValidator('json', bodySchema), async (c) => {
const body = c.req.valid('json') // Typed and validated
// body.title, body.content, body.tags — all typed correctly
return c.json({ post: body }, 201)
})
Validation errors are returned automatically with a 400 status. Use the JWT Debugger and CORS Policy Builder tools when debugging auth and CORS issues in your Hono API.
Error Handling
Hono provides a clean error handling API that works across all runtimes:
import { Hono, HTTPException } from 'hono'
const app = new Hono()
// Custom error handler
app.onError((err, c) => {
if (err instanceof HTTPException) {
return err.getResponse()
}
console.error('Unhandled error:', err)
return c.json({ error: 'Internal Server Error' }, 500)
})
// 404 handler
app.notFound((c) => {
return c.json({ error: `Not Found: ${c.req.path}` }, 404)
})
// Throw typed HTTP errors anywhere
app.get('/admin', (c) => {
const role = c.get('user')?.role
if (role !== 'admin') {
throw new HTTPException(403, { message: 'Forbidden' })
}
return c.json({ admin: true })
})
Testing
Hono apps are trivial to test because app.fetch is a standard fetch handler:
import { describe, it, expect } from 'vitest'
import app from './index'
describe('API', () => {
it('GET / returns 200', async () => {
const res = await app.request('/')
expect(res.status).toBe(200)
})
it('POST /users creates a user', async () => {
const res = await app.request('/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice', email: '[email protected]' }),
})
expect(res.status).toBe(201)
const data = await res.json()
expect(data.user.name).toBe('Alice')
})
})
No server to start, no port conflicts — app.request processes requests in memory. This makes CI fast and test setup minimal.
When to Choose Hono
Hono is the right choice when:
- You’re targeting Cloudflare Workers, Deno Deploy, or Bun
- You need a type-safe API with zero dependency overhead
- You want the RPC client to replace a REST client or avoid a full tRPC setup
- You’re building edge functions that need to stay small and fast
- You’re unsure which runtime you’ll ultimately deploy to
Consider alternatives when:
- You need the full Node.js ecosystem and heavy ORM integrations (Fastify or NestJS)
- You’re all-in on Bun and want maximum Bun-specific DX (Elysia)
- You need a full-stack React framework (Next.js, Remix)
Summary
Hono.js in 2026 is a mature, production-ready framework for teams that need speed, type safety, and runtime flexibility. Its zero-dependency design, TypeScript-first API, built-in middleware suite, and RPC client cover the full stack without the bloat of more opinionated frameworks.
Start with npm create hono@latest, pick your runtime at deploy time, and use the RPC client to eliminate manual API client maintenance. Whether you deploy to Cloudflare, Deno, Bun, or Node, the same application code runs everywhere.
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.