Hono Guide
Hono is a small, simple, and ultrafast web framework built on Web Standards. It works on any JavaScript runtime: Cloudflare Workers, Bun, Deno, Node.js, AWS Lambda, Vercel, Netlify, Fastly Compute, and more. The same code runs on all platforms.
Quick Start
npm create hono@latest my-app
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export default app
Core Concepts
Routing
// HTTP methods
app.get('/posts', (c) => c.json({ posts }))
app.post('/posts', (c) => c.json({ message: 'Created' }, 201))
app.put('/posts/:id', (c) => c.json({ message: 'Updated' }))
app.delete('/posts/:id', (c) => c.json({ message: 'Deleted' }))
// Path parameters
app.get('/users/:id', (c) => {
const id = c.req.param('id') // inferred type
return c.json({ id })
})
// Optional params
app.get('/api/animal/:type?', (c) => c.text('Animal!'))
// Wildcard
app.get('/wild/*/card', (c) => c.text('Matched!'))
// Regexp
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
const { date, title } = c.req.param()
return c.json({ date, title })
})
// Chained routes
app.get('/endpoint', (c) => c.text('GET'))
.post((c) => c.text('POST'))
.delete((c) => c.text('DELETE'))
Context API (c)
The Context object provides all request/response methods:
// Response methods
c.text('Hello') // text/plain
c.json({ message: 'Hello' }) // application/json
c.html('<h1>Hello</h1>') // text/html
c.redirect('/new-path') // 302 redirect
c.redirect('/new-path', 301) // 301 redirect
c.notFound() // 404
c.body(data, 200, headers) // raw response
// Status & headers
c.status(201)
c.header('X-Custom', 'value')
// Request data
c.req.param('id') // path param
c.req.query('q') // query string
c.req.queries('tags') // multiple values
c.req.header('Authorization') // header
const body = await c.req.json() // JSON body
const form = await c.req.parseBody() // form data
// Variables (pass data between middleware and handlers)
c.set('user', userObj)
const user = c.get('user')
// or: c.var.user
// Environment (Cloudflare bindings, env vars)
c.env.MY_KV // KV namespace
c.env.DATABASE_URL // env variable
Middleware
Middleware runs before/after handlers in onion-layer order:
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { basicAuth } from 'hono/basic-auth'
// Apply to all routes
app.use(logger())
app.use(cors())
// Apply to specific paths
app.use('/api/*', cors({ origin: 'https://example.com' }))
app.use('/admin/*', basicAuth({ username: 'admin', password: 'secret' }))
// Custom middleware
app.use(async (c, next) => {
const start = Date.now()
await next()
c.header('X-Response-Time', `${Date.now() - start}ms`)
})
Execution order: middleware 1 start -> middleware 2 start -> handler -> middleware 2 end -> middleware 1 end
Built-in Middleware (import from hono/<name>)
| Middleware |
Import |
Purpose |
basicAuth |
hono/basic-auth |
HTTP Basic authentication |
bearerAuth |
hono/bearer-auth |
Bearer token authentication |
jwt |
hono/jwt |
JWT authentication |
cors |
hono/cors |
CORS headers |
csrf |
hono/csrf |
CSRF protection |
logger |
hono/logger |
Request logging |
secureHeaders |
hono/secure-headers |
Security headers (Helmet-like) |
etag |
hono/etag |
ETag caching |
cache |
hono/cache |
Cache API (CF Workers, Deno) |
compress |
hono/compress |
Response compression |
bodyLimit |
hono/body-limit |
Request body size limit |
timeout |
hono/timeout |
Request timeout |
prettyJSON |
hono/pretty-json |
Pretty-print JSON with ?pretty |
requestId |
hono/request-id |
Unique request ID per request |
ipRestriction |
hono/ip-restriction |
IP allow/deny lists |
languageDetector |
hono/language |
i18n language detection |
jsxRenderer |
hono/jsx-renderer |
JSX layout renderer |
contextStorage |
hono/context-storage |
AsyncLocalStorage for Context |
methodOverride |
hono/method-override |
HTTP method override |
timing |
hono/timing |
Server-Timing header |
Helpers (import from hono/<name>)
| Helper |
Import |
Purpose |
| Cookie |
hono/cookie |
get/set/delete cookies |
| JWT |
hono/jwt |
sign/verify/decode JWT |
| Streaming |
hono/streaming |
stream, streamText, streamSSE |
| WebSocket |
Platform-specific |
upgradeWebSocket handler |
| HTML |
hono/html |
html template literals |
| CSS |
hono/css |
CSS-in-JS(X) |
| Factory |
hono/factory |
createMiddleware, createHandlers |
| Testing |
hono/testing |
testClient for typed testing |
| Proxy |
hono/proxy |
Reverse proxy helper |
| SSG |
hono/ssg |
Static site generation |
| Accepts |
hono/accepts |
Content negotiation (Accept-*) |
| Adapter |
hono/adapter |
env(), getRuntimeKey() |
| ConnInfo |
Platform-specific |
Client remote address, connection info |
| Dev |
hono/dev |
showRoutes(), getRouterName() |
| Route |
hono/route |
matchedRoutes(), routePath() |
Larger Applications
Use app.route() to split into sub-apps:
// authors.ts
const authors = new Hono()
.get('/', (c) => c.json('list authors'))
.post('/', (c) => c.json('create author', 201))
.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default authors
// index.ts
import authors from './authors'
import books from './books'
const app = new Hono()
app.route('/authors', authors)
app.route('/books', books)
export default app
Type-Safe RPC
Share API types between server and client:
// server.ts
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const route = app.post('/posts',
zValidator('form', z.object({ title: z.string(), body: z.string() })),
(c) => c.json({ ok: true, message: 'Created!' }, 201)
)
export type AppType = typeof route
// client.ts
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('http://localhost:8787/')
const res = await client.posts.$post({
form: { title: 'Hello', body: 'World' }
})
Key RPC rule: chain route definitions for type inference to work.
Validation
import { validator } from 'hono/validator'
app.post('/posts',
validator('json', (value, c) => {
if (!value.title) return c.text('Invalid!', 400)
return { title: value.title }
}),
(c) => {
const { title } = c.req.valid('json')
return c.json({ title }, 201)
}
)
Validation targets: json, form, query, header, param, cookie.
Presets
| Preset |
Import |
Use Case |
hono (default) |
import { Hono } from 'hono' |
Most cases, long-lived servers |
hono/quick |
import { Hono } from 'hono/quick' |
Per-request initialization |
hono/tiny |
import { Hono } from 'hono/tiny' |
Under 14KB, resource-limited |
Platform Handler Patterns
// Cloudflare Workers / Bun - export default
export default app
// Node.js
import { serve } from '@hono/node-server'
serve(app)
// AWS Lambda
import { handle } from 'hono/aws-lambda'
export const handler = handle(app)
// Deno
Deno.serve(app.fetch)
// Vercel / Next.js
import { handle } from 'hono/vercel'
export const GET = handle(app)
export const POST = handle(app)
// Netlify
import { handle } from 'hono/netlify'
export default handle(app)
Testing
// Use app.request() for testing
const res = await app.request('/posts')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ posts: [] })
// POST with JSON
const res = await app.request('/posts', {
method: 'POST',
body: JSON.stringify({ title: 'Hello' }),
headers: { 'Content-Type': 'application/json' },
})
// Mock env (3rd argument)
const res = await app.request('/posts', {}, { API_KEY: 'test' })
Key Rules
- Don't create RoR-like controllers - define handlers inline for type inference
- Chain routes for RPC type inference to work:
const app = new Hono().get(...).post(...)
- Middleware order matters - registered first runs first (before next), last (after next)
- Export
typeof route not typeof app for RPC
- For RPC with
app.route(): chain the .route() calls and export the chained result: const routes = app.route('/a', a).route('/b', b); export type AppType = typeof routes
- Use lowercase header names when validating headers
- Set Content-Type header when testing
json or form validators
next() never throws - Hono catches errors and passes to app.onError()
- Route registration order matters - register sub-routes before mounting with
app.route()
Common Errors
- Empty body in validator: Missing
Content-Type header in request
- RPC types not working: Routes not chained, or Hono version mismatch between client/server
- 404 on sub-routes: Routes registered after
app.route() call (wrong order)
- Streaming not working on CF Workers: Add
c.header('Content-Encoding', 'Identity')
- WebSocket + CORS conflict:
upgradeWebSocket() modifies headers internally, conflicts with header-modifying middleware
- Slow IDE with RPC: Too many routes cause excessive type instantiation. Fix: ensure matching Hono versions, split clients per sub-app, or pre-compile types with
hcWithType pattern (see references/rpc-validation.md Section 21)
Reference Files
references/api-reference.md - Context, HonoRequest, App, HTTPException, Routing
references/middleware-auth.md - Middleware concepts, Auth (Basic, Bearer, JWT, JWK)
references/middleware-security.md - Security (CORS, CSRF, Secure Headers, IP Restriction), Access Control (Combine), Custom Middleware, Best Practices
references/middleware-request-response.md - Request Processing (BodyLimit, Compress, MethodOverride, TrailingSlash), Response Processing (Cache, ETag, PrettyJSON)
references/middleware-utilities.md - Utilities (ContextStorage, Logger, RequestID, Timing, Timeout), Rendering (JSXRenderer), i18n (Language)
references/helpers-auth-streaming.md - Cookie, JWT (sign/verify/decode, all algorithms), Streaming (stream, streamText, streamSSE), WebSocket
references/helpers-rendering.md - HTML (tagged templates, raw, XSS protection), CSS (scoped styles, keyframes, cx, global styles, CSP nonce)
references/helpers-factory-testing.md - Factory, Testing (testClient), Proxy, SSG
references/helpers-runtime.md - Accepts (content negotiation), Adapter (env, getRuntimeKey), ConnInfo, Dev (showRoutes), Route
references/platforms-core.md - Cloudflare Workers, Cloudflare Pages, Bun, Deno, Node.js
references/platforms-serverless.md - AWS Lambda, Lambda@Edge, Vercel, Next.js, Netlify
references/platforms-other.md - Azure, GCR, Fastly, Supabase, Alibaba, Service Worker, WebAssembly, Platform Comparison
references/rpc-validation.md - RPC client, validators, Zod, Standard Schema
references/jsx.md - JSX, Client Components, JSX Renderer, Suspense, streaming
references/patterns.md - Best practices, testing, error handling, validation patterns, RPC troubleshooting (hcWithType), View Transitions, Service Worker
1---2name: hono-guide3description: Guide for Hono, an ultrafast web framework built on Web Standards. Use when user asks to "create a Hono app", "build an API with Hono", "add Hono middleware", "deploy Hono to Cloudflare Workers", "use Hono RPC", "add auth to Hono", "validate requests in Hono", "use Hono JSX", or asks about Hono routing, context, streaming, WebSocket, CORS, testing, SSG, or multi-runtime deployment. Do NOT use for Express.js, Fastify, Koa, or Nest.js.4---56# Hono Guide78Hono is a small, simple, and ultrafast web framework built on Web Standards. It works on any JavaScript runtime: Cloudflare Workers, Bun, Deno, Node.js, AWS Lambda, Vercel, Netlify, Fastly Compute, and more. The same code runs on all platforms.910## Quick Start1112```bash13npm create hono@latest my-app14```1516```ts17import { Hono } from 'hono'18const app = new Hono()1920app.get('/', (c) => c.text('Hello Hono!'))2122export default app23```2425## Core Concepts2627### Routing2829```ts30// HTTP methods31app.get('/posts', (c) => c.json({ posts }))32app.post('/posts', (c) => c.json({ message: 'Created' }, 201))33app.put('/posts/:id', (c) => c.json({ message: 'Updated' }))34app.delete('/posts/:id', (c) => c.json({ message: 'Deleted' }))3536// Path parameters37app.get('/users/:id', (c) => {38 const id = c.req.param('id') // inferred type39 return c.json({ id })40})4142// Optional params43app.get('/api/animal/:type?', (c) => c.text('Animal!'))4445// Wildcard46app.get('/wild/*/card', (c) => c.text('Matched!'))4748// Regexp49app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {50 const { date, title } = c.req.param()51 return c.json({ date, title })52})5354// Chained routes55app.get('/endpoint', (c) => c.text('GET'))56 .post((c) => c.text('POST'))57 .delete((c) => c.text('DELETE'))58```5960### Context API (c)6162The `Context` object provides all request/response methods:6364```ts65// Response methods66c.text('Hello') // text/plain67c.json({ message: 'Hello' }) // application/json68c.html('<h1>Hello</h1>') // text/html69c.redirect('/new-path') // 302 redirect70c.redirect('/new-path', 301) // 301 redirect71c.notFound() // 40472c.body(data, 200, headers) // raw response7374// Status & headers75c.status(201)76c.header('X-Custom', 'value')7778// Request data79c.req.param('id') // path param80c.req.query('q') // query string81c.req.queries('tags') // multiple values82c.req.header('Authorization') // header83const body = await c.req.json() // JSON body84const form = await c.req.parseBody() // form data8586// Variables (pass data between middleware and handlers)87c.set('user', userObj)88const user = c.get('user')89// or: c.var.user9091// Environment (Cloudflare bindings, env vars)92c.env.MY_KV // KV namespace93c.env.DATABASE_URL // env variable94```9596### Middleware9798Middleware runs before/after handlers in onion-layer order:99100```ts101import { logger } from 'hono/logger'102import { cors } from 'hono/cors'103import { basicAuth } from 'hono/basic-auth'104105// Apply to all routes106app.use(logger())107app.use(cors())108109// Apply to specific paths110app.use('/api/*', cors({ origin: 'https://example.com' }))111app.use('/admin/*', basicAuth({ username: 'admin', password: 'secret' }))112113// Custom middleware114app.use(async (c, next) => {115 const start = Date.now()116 await next()117 c.header('X-Response-Time', `${Date.now() - start}ms`)118})119```120121**Execution order**: middleware 1 start -> middleware 2 start -> handler -> middleware 2 end -> middleware 1 end122123### Built-in Middleware (import from `hono/<name>`)124125| Middleware | Import | Purpose |126|---|---|---|127| `basicAuth` | `hono/basic-auth` | HTTP Basic authentication |128| `bearerAuth` | `hono/bearer-auth` | Bearer token authentication |129| `jwt` | `hono/jwt` | JWT authentication |130| `cors` | `hono/cors` | CORS headers |131| `csrf` | `hono/csrf` | CSRF protection |132| `logger` | `hono/logger` | Request logging |133| `secureHeaders` | `hono/secure-headers` | Security headers (Helmet-like) |134| `etag` | `hono/etag` | ETag caching |135| `cache` | `hono/cache` | Cache API (CF Workers, Deno) |136| `compress` | `hono/compress` | Response compression |137| `bodyLimit` | `hono/body-limit` | Request body size limit |138| `timeout` | `hono/timeout` | Request timeout |139| `prettyJSON` | `hono/pretty-json` | Pretty-print JSON with `?pretty` |140| `requestId` | `hono/request-id` | Unique request ID per request |141| `ipRestriction` | `hono/ip-restriction` | IP allow/deny lists |142| `languageDetector` | `hono/language` | i18n language detection |143| `jsxRenderer` | `hono/jsx-renderer` | JSX layout renderer |144| `contextStorage` | `hono/context-storage` | AsyncLocalStorage for Context |145| `methodOverride` | `hono/method-override` | HTTP method override |146| `timing` | `hono/timing` | Server-Timing header |147148### Helpers (import from `hono/<name>`)149150| Helper | Import | Purpose |151|---|---|---|152| Cookie | `hono/cookie` | get/set/delete cookies |153| JWT | `hono/jwt` | sign/verify/decode JWT |154| Streaming | `hono/streaming` | stream, streamText, streamSSE |155| WebSocket | Platform-specific | upgradeWebSocket handler |156| HTML | `hono/html` | html template literals |157| CSS | `hono/css` | CSS-in-JS(X) |158| Factory | `hono/factory` | createMiddleware, createHandlers |159| Testing | `hono/testing` | testClient for typed testing |160| Proxy | `hono/proxy` | Reverse proxy helper |161| SSG | `hono/ssg` | Static site generation |162| Accepts | `hono/accepts` | Content negotiation (Accept-*) |163| Adapter | `hono/adapter` | env(), getRuntimeKey() |164| ConnInfo | Platform-specific | Client remote address, connection info |165| Dev | `hono/dev` | showRoutes(), getRouterName() |166| Route | `hono/route` | matchedRoutes(), routePath() |167168### Larger Applications169170Use `app.route()` to split into sub-apps:171172```ts173// authors.ts174const authors = new Hono()175 .get('/', (c) => c.json('list authors'))176 .post('/', (c) => c.json('create author', 201))177 .get('/:id', (c) => c.json(`get ${c.req.param('id')}`))178export default authors179180// index.ts181import authors from './authors'182import books from './books'183184const app = new Hono()185app.route('/authors', authors)186app.route('/books', books)187export default app188```189190### Type-Safe RPC191192Share API types between server and client:193194```ts195// server.ts196import { zValidator } from '@hono/zod-validator'197import { z } from 'zod'198199const route = app.post('/posts',200 zValidator('form', z.object({ title: z.string(), body: z.string() })),201 (c) => c.json({ ok: true, message: 'Created!' }, 201)202)203export type AppType = typeof route204205// client.ts206import { hc } from 'hono/client'207import type { AppType } from './server'208209const client = hc<AppType>('http://localhost:8787/')210const res = await client.posts.$post({211 form: { title: 'Hello', body: 'World' }212})213```214215**Key RPC rule**: chain route definitions for type inference to work.216217### Validation218219```ts220import { validator } from 'hono/validator'221222app.post('/posts',223 validator('json', (value, c) => {224 if (!value.title) return c.text('Invalid!', 400)225 return { title: value.title }226 }),227 (c) => {228 const { title } = c.req.valid('json')229 return c.json({ title }, 201)230 }231)232```233234Validation targets: `json`, `form`, `query`, `header`, `param`, `cookie`.235236### Presets237238| Preset | Import | Use Case |239|---|---|---|240| `hono` (default) | `import { Hono } from 'hono'` | Most cases, long-lived servers |241| `hono/quick` | `import { Hono } from 'hono/quick'` | Per-request initialization |242| `hono/tiny` | `import { Hono } from 'hono/tiny'` | Under 14KB, resource-limited |243244### Platform Handler Patterns245246```ts247// Cloudflare Workers / Bun - export default248export default app249250// Node.js251import { serve } from '@hono/node-server'252serve(app)253254// AWS Lambda255import { handle } from 'hono/aws-lambda'256export const handler = handle(app)257258// Deno259Deno.serve(app.fetch)260261// Vercel / Next.js262import { handle } from 'hono/vercel'263export const GET = handle(app)264export const POST = handle(app)265266// Netlify267import { handle } from 'hono/netlify'268export default handle(app)269```270271### Testing272273```ts274// Use app.request() for testing275const res = await app.request('/posts')276expect(res.status).toBe(200)277expect(await res.json()).toEqual({ posts: [] })278279// POST with JSON280const res = await app.request('/posts', {281 method: 'POST',282 body: JSON.stringify({ title: 'Hello' }),283 headers: { 'Content-Type': 'application/json' },284})285286// Mock env (3rd argument)287const res = await app.request('/posts', {}, { API_KEY: 'test' })288```289290## Key Rules2912921. **Don't create RoR-like controllers** - define handlers inline for type inference2932. **Chain routes** for RPC type inference to work: `const app = new Hono().get(...).post(...)`2943. **Middleware order matters** - registered first runs first (before next), last (after next)2954. **Export `typeof route`** not `typeof app` for RPC2965. **For RPC with `app.route()`**: chain the `.route()` calls and export the chained result: `const routes = app.route('/a', a).route('/b', b); export type AppType = typeof routes`2976. **Use lowercase header names** when validating headers2987. **Set Content-Type header** when testing `json` or `form` validators2998. **`next()` never throws** - Hono catches errors and passes to `app.onError()`3009. **Route registration order** matters - register sub-routes before mounting with `app.route()`301302## Common Errors3033041. **Empty body in validator**: Missing `Content-Type` header in request3052. **RPC types not working**: Routes not chained, or Hono version mismatch between client/server3063. **404 on sub-routes**: Routes registered after `app.route()` call (wrong order)3074. **Streaming not working on CF Workers**: Add `c.header('Content-Encoding', 'Identity')`3085. **WebSocket + CORS conflict**: `upgradeWebSocket()` modifies headers internally, conflicts with header-modifying middleware3096. **Slow IDE with RPC**: Too many routes cause excessive type instantiation. Fix: ensure matching Hono versions, split clients per sub-app, or pre-compile types with `hcWithType` pattern (see `references/rpc-validation.md` Section 21)310311## Reference Files312313- `references/api-reference.md` - Context, HonoRequest, App, HTTPException, Routing314- `references/middleware-auth.md` - Middleware concepts, Auth (Basic, Bearer, JWT, JWK)315- `references/middleware-security.md` - Security (CORS, CSRF, Secure Headers, IP Restriction), Access Control (Combine), Custom Middleware, Best Practices316- `references/middleware-request-response.md` - Request Processing (BodyLimit, Compress, MethodOverride, TrailingSlash), Response Processing (Cache, ETag, PrettyJSON)317- `references/middleware-utilities.md` - Utilities (ContextStorage, Logger, RequestID, Timing, Timeout), Rendering (JSXRenderer), i18n (Language)318- `references/helpers-auth-streaming.md` - Cookie, JWT (sign/verify/decode, all algorithms), Streaming (stream, streamText, streamSSE), WebSocket319- `references/helpers-rendering.md` - HTML (tagged templates, raw, XSS protection), CSS (scoped styles, keyframes, cx, global styles, CSP nonce)320- `references/helpers-factory-testing.md` - Factory, Testing (testClient), Proxy, SSG321- `references/helpers-runtime.md` - Accepts (content negotiation), Adapter (env, getRuntimeKey), ConnInfo, Dev (showRoutes), Route322- `references/platforms-core.md` - Cloudflare Workers, Cloudflare Pages, Bun, Deno, Node.js323- `references/platforms-serverless.md` - AWS Lambda, Lambda@Edge, Vercel, Next.js, Netlify324- `references/platforms-other.md` - Azure, GCR, Fastly, Supabase, Alibaba, Service Worker, WebAssembly, Platform Comparison325- `references/rpc-validation.md` - RPC client, validators, Zod, Standard Schema326- `references/jsx.md` - JSX, Client Components, JSX Renderer, Suspense, streaming327- `references/patterns.md` - Best practices, testing, error handling, validation patterns, RPC troubleshooting (hcWithType), View Transitions, Service Worker