
Hono
- 49 installs
- 2 repo stars
- Updated August 3, 2026
- fandhe-ai/agent-reference-skills
Helps with ai & agent building tasks.
About
hono is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- hono
- AI & Agent Building
- AI-coding skill
Hono by the numbers
- 49 all-time installs (skills.sh)
- Ranked #7,391 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/fandhe-ai/agent-reference-skills --skill honoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 49 |
|---|---|
| repo stars | ★ 2 |
| Last updated | August 3, 2026 |
| Repository | fandhe-ai/agent-reference-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Hono API リファレンス
Hono — Web Standards ベースの軽量・高速 Web フレームワーク。 マルチランタイム(Cloudflare Workers, Deno, Bun, Node.js 等)で動作する。 API 設計・ミドルウェア選定・ルーティング実装時に参照する。
ディレクトリ構成
skills/hono/
SKILL.md
references/
getting-started/
README.md
basic.md
nodejs.md
bun.md
deno.md
cloudflare-workers.md
cloudflare-pages.md
vercel.md
netlify.md
aws-lambda.md
lambda-edge.md
fastly.md
azure-functions.md
google-cloud-run.md
nextjs.md
supabase-functions.md
service-worker.md
api/
README.md
hono.md
context.md
request.md
routing.md
exception.md
presets.md
concepts/
README.md
motivation.md
web-standard.md
middleware.md
routers.md
benchmarks.md
developer-experience.md
stacks.md
middleware/
README.md
basic-auth.md
bearer-auth.md
jwt.md
jwk.md
csrf.md
secure-headers.md
cors.md
etag.md
body-limit.md
cache.md
compress.md
pretty-json.md
method-override.md
request-id.md
timeout.md
timing.md
logger.md
language.md
trailing-slash.md
context-storage.md
jsx-renderer.md
combine.md
ip-restriction.md
helpers/
README.md
accepts.md
adapter.md
conninfo.md
cookie.md
css.md
dev.md
factory.md
html.md
jwt.md
proxy.md
route.md
ssg.md
streaming.md
testing.md
websocket.md
guides/
README.md
best-practices.md
examples.md
jsx.md
jsx-dom.md
middleware.md
others.md
rpc.md
testing.md
validation.md
samples/
README.md
basic-app.md
cors-jwt-auth.md
middleware-custom.md
jsx-ssr.md
rpc.md
routing.md
streaming-sse.md
testing.md
validation-zod.md
websocket.md
scripts/
README.md
install.md
dev.md
build.md
deploy.md
test.md探索手順
タスクからカテゴリを引き、カテゴリの README.md で目的のページを特定する:
1. 下記マッピング表でタスクに対応するカテゴリを探す 2. そのカテゴリの references/{category}/README.md を参照して目的のページを特定する 3. 該当ページの .md を Read して詳細を確認する
タスク → カテゴリ マッピング
| タスク | カテゴリ | 参照 README |
|---|---|---|
| プロジェクト初期セットアップ、ランタイム別の構成、serve 関数 | getting-started | references/getting-started/README.md |
| Hono App, HonoRequest, Context, ルーティング, HTTPException, プリセット | api | references/api/README.md |
| 設計思想、Web Standards、ミドルウェア概念、ルーター種類、Stacks | concepts | references/concepts/README.md |
| 認証 (JWT / Basic / Bearer)、CORS、CSRF、Cache、Logger、Compress、ETag 等 | middleware | references/middleware/README.md |
| Cookie、Streaming、WebSocket、Proxy、Factory、SSG、Testing、ConnInfo | helpers | references/helpers/README.md |
| ベストプラクティス、カスタムミドルウェア、JSX、バリデーション、RPC、テスト | guides | references/guides/README.md |
| 典型的な使い方を知りたい | samples | samples/README.md |
| インストール・CLI コマンドを知りたい | scripts | scripts/README.md |
Context
The Context object (c) is passed to every handler and middleware. It wraps the incoming request and provides helpers for building responses and managing request-scoped state.
Signature / Usage
app.get('/hello', (c) => {
const name = c.req.query('name')
return c.json({ message: `Hello, ${name}` })
})Properties
| Name | Type | Description |
|---|---|---|
c.req | HonoRequest | The incoming request wrapper |
c.res | Response | The response that will be returned; can be mutated to set headers |
c.env | Bindings | Runtime environment (Cloudflare Workers bindings, Deno env, etc.) |
c.event | FetchEvent | Service Worker FetchEvent — not recommended, use c.executionCtx |
c.executionCtx | ExecutionContext | Cloudflare Workers execution context (e.g. waitUntil) |
c.error | `Error \ | undefined` |
c.var | Variables | Proxy for typed access to values set with c.set() |
Response Methods
c.text()
Return a plain-text response (Content-Type: text/plain).
return c.text('Hello!')
return c.text('Created', 201)c.json()
Return a JSON response (Content-Type: application/json).
return c.json({ message: 'Hello!' })
return c.json({ error: 'Not found' }, 404)c.html()
Return an HTML response (Content-Type: text/html).
return c.html('<h1>Hello!</h1>')c.body()
Return a raw response with full control over status and headers.
return c.body('Thank you', 201, { 'X-Message': 'Hello!' })Prefer c.text() / c.html() / c.json() for common content types.
c.redirect()
Return a redirect response. Default status is 302.
return c.redirect('/')
return c.redirect('/login', 301)c.notFound()
Return a 404 Not Found response.
return c.notFound()Response Modifier Methods
c.status()
Set the HTTP status code for the next response helper call.
c.status(201)
return c.text('Created')Default status is 200.
c.header()
Set a single response header.
c.header('X-Message', 'Hello')
c.header('Content-Type', 'application/octet-stream')Request-Scoped State
c.set() / c.get()
Store and retrieve arbitrary values for the duration of a single request.
// In middleware:
c.set('user', { id: 1, name: 'Alice' })
// In handler:
const user = c.get('user')Values are not shared across requests.
c.var
Typed dot-notation access to values set via c.set(). Requires Variables type on the Hono instance.
const app = new Hono<{ Variables: { user: User } }>()
// ...
const user = c.var.userRendering
c.setRenderer()
Define a layout template in middleware for c.render().
app.use((c, next) => {
c.setRenderer((content) =>
c.html(`<html><body>${content}</body></html>`)
)
return next()
})c.render()
Render content through the previously set renderer.
return c.render('<h1>Hello</h1>')Notes
c.resmutations must happen before the response is sent; best done in middlewarec.event(Service Worker syntax) is discouraged; preferc.executionCtxc.executionCtx.waitUntil()defers async work beyond the response lifecycle
Related
- request.md
- hono.md
HTTPException
A specialized Error subclass for throwing HTTP errors with a status code, human-readable message, optional custom Response, and an optional cause.
Signature / Usage
import { HTTPException } from 'hono/http-exception'
throw new HTTPException(401, { message: 'Unauthorized' })Options / Props
| Name | Type | Description |
|---|---|---|
status | number (1st arg) | HTTP status code |
message | string | Short error message for simple responses |
res | Response | Custom Response object (overrides message) |
cause | unknown | Arbitrary value attached as Error.cause |
Throwing
With a message
throw new HTTPException(401, { message: 'Unauthorized' })With a custom Response
Use when you need non-standard headers or a structured body.
const errorResponse = new Response('Unauthorized', {
status: 401,
headers: { Authenticate: 'error="invalid_token"' },
})
throw new HTTPException(401, { res: errorResponse })With a cause
Chain to the original error for debugging.
try {
await authorize(c)
} catch (cause) {
throw new HTTPException(401, { message: 'Unauthorized', cause })
}Handling
Use app.onError() to intercept HTTPException instances. Call err.getResponse() to convert the exception into a Response.
app.onError((err, c) => {
if (err instanceof HTTPException) {
return err.getResponse()
}
console.error(err)
return c.text('Internal Server Error', 500)
})Notes
getResponse()is not context-aware; manually merge context-specific headers into the returnedResponseif needed- When
resis provided,getResponse()returns thatResponsedirectly
Related
- hono.md
- context.md
Hono App
The main application class. Provides routing, middleware registration, error handling, and execution entry points.
Signature / Usage
import { Hono } from 'hono'
const app = new Hono()
const app = new Hono({ strict: false })
const app = new Hono({ router: new RegExpRouter() })
const app = new Hono<{ Bindings: Env; Variables: Vars }>()Options / Props
| Name | Type | Description |
|---|---|---|
strict | boolean | Distinguish /hello from /hello/. Default: true |
router | Router | Override the router implementation (e.g. RegExpRouter, TrieRouter) |
Methods
HTTP routing methods
app.get(path, ...handlers)
app.post(path, ...handlers)
app.put(path, ...handlers)
app.delete(path, ...handlers)
app.patch(path, ...handlers)
app.all(path, ...handlers)
app.on(method | method[], path | path[], ...handlers)All routing methods return the Hono instance (chainable).
app.get('/hello', (c) => c.text('Hello'))
app.on(['PUT', 'DELETE'], '/post/:id', (c) => c.text('Updated or deleted'))use()
Register global or path-scoped middleware.
app.use(middleware)
app.use(path, middleware)route()
Mount a sub-application under a path prefix.
const api = new Hono()
api.get('/users', (c) => c.json([]))
app.route('/api', api) // → GET /api/usersbasePath()
Set an application-wide base path. Returns a new Hono instance.
const api = new Hono().basePath('/api')
api.get('/book', (c) => c.text('List')) // → GET /api/bookmount()
Mount an application built with another framework.
app.mount('/another-app', anotherFrameworkHandler)notFound()
Customize the 404 response. Only called from the top-level app.
app.notFound((c) => c.text('Custom 404', 404))onError()
Global error handler. Route-level handlers take priority over parent handlers.
app.onError((err, c) => {
console.error(err)
return c.text('Internal Server Error', 500)
})fetch()
The application entry point (Cloudflare Workers, Bun, Deno).
export default app
// or explicitly:
export default { fetch: app.fetch }request()
Send a test request. Useful in unit tests.
const res = await app.request('/hello')
const res = await app.request('http://localhost/hello', { method: 'POST' })showRoutes()
Print all registered routes to the console (for debugging).
app.showRoutes()routerName
Read-only property returning the name of the active router.
console.log(app.routerName) // e.g. "SmartRouter"fire() (deprecated)
Automatically adds a global fetch event listener for Service Worker environments. Use fire() from hono/service-worker instead.
Notes
strict: falsemakes/helloand/hello/equivalent- Generic type parameters
BindingsandVariablesenable type-safec.envandc.varaccess notFound()andonError()are only effective when registered on the top-level app
Related
- context.md
- request.md
- routing.md
Presets
Hono ships three preset entry points that differ only in the underlying router implementation. All expose the same Hono class API.
Signature / Usage
import { Hono } from 'hono' // default — SmartRouter (RegExpRouter + TrieRouter)
import { Hono } from 'hono/quick' // SmartRouter (LinearRouter + TrieRouter)
import { Hono } from 'hono/tiny' // PatternRouterOptions / Props
| Preset | Router | Best for |
|---|---|---|
hono | SmartRouter (RegExpRouter + TrieRouter) | Long-running servers; best runtime performance after warm-up |
hono/quick | SmartRouter (LinearRouter + TrieRouter) | Environments where the app is re-initialized per request (edge functions) |
hono/tiny | PatternRouter | Resource-constrained environments; smallest bundle size |
Notes
hono(default) is recommended for most use cases: Cloudflare Workers, Bun, Deno, Node.js, Fastly Computehono/quickprioritizes fast startup over peak throughput — suited to request-per-initialization runtimeshono/tinytrades routing capability for minimal overhead; not suitable for complex routing needs- The API surface is identical across all three presets; switching is a one-line import change
Related
- hono.md
API
| Name | Description | Path |
|---|---|---|
| Context | The Context object (c) is passed to every handler and middleware. | context.md |
| HTTPException | A specialized Error subclass for throwing HTTP errors with a status code… | exception.md |
| Hono App | The main application class. Provides routing, middleware registration… | hono.md |
| HonoRequest | Wrapper around the standard Request object, accessible via c.req… | request.md |
| Presets | Hono ships three preset entry points that differ only in the underlying… | presets.md |
| Routing | Hono's routing system supports HTTP method matching, path parameters… | routing.md |
HonoRequest
Wrapper around the standard Request object, accessible via c.req in handlers. Provides typed helpers for extracting path params, query strings, headers, and request bodies.
Signature / Usage
// Accessed through the Context object
app.get('/user/:id', (c) => {
const id = c.req.param('id')
return c.text(id)
})Methods
param()
Retrieve path parameters.
param(key: string): string
param(): Record<string, string>const id = c.req.param('id')
const { id, commentId } = c.req.param()query()
Retrieve query string parameters.
query(key: string): string | undefined
query(): Record<string, string>const q = c.req.query('q')
const { q, limit } = c.req.query()queries()
Retrieve multiple values for the same query key (e.g. ?tags=A&tags=B).
queries(key: string): string[]const tags = c.req.queries('tags') // ['A', 'B']header()
Retrieve request headers.
header(name: string): string | undefined
header(): Record<string, string>const ua = c.req.header('User-Agent')When called without arguments, all returned keys are lowercase.
parseBody()
Parse multipart/form-data or application/x-www-form-urlencoded bodies.
parseBody(options?: { all?: boolean; dot?: boolean }): Promise<Record<string, string | File | (string | File)[]>>const body = await c.req.parseBody()
const body = await c.req.parseBody({ all: true }) // handle duplicate field names
const body = await c.req.parseBody({ dot: true }) // foo.bar → { foo: { bar: ... } }Use the foo[] field name suffix to receive multiple files as an array.
json()
Parse a JSON request body.
json<T = unknown>(): Promise<T>const data = await c.req.json<{ name: string }>()text()
Parse a plain-text request body.
text(): Promise<string>arrayBuffer()
Parse the request body as an ArrayBuffer.
arrayBuffer(): Promise<ArrayBuffer>blob()
Parse the request body as a Blob.
blob(): Promise<Blob>formData()
Parse the request body as a FormData object.
formData(): Promise<FormData>valid()
Retrieve data that has been validated by a validator middleware.
valid(target: 'form' | 'json' | 'query' | 'header' | 'cookie' | 'param'): Record<string, unknown>const { title, body } = c.req.valid('form')cloneRawRequest()
Clone the raw Request object after the body has been consumed.
cloneRawRequest(): RequestUseful when you need to pass the original request to another function after reading the body.
Properties
| Name | Type | Description |
|---|---|---|
url | string | Full request URL including protocol and query string |
path | string | Pathname portion of the URL (no query string) |
method | string | HTTP method in uppercase (e.g. GET) |
raw | Request | The underlying Web API Request object |
matchedRoutes | RouteData[] | Array of matched routes with handler, method, path — deprecated in v4.8.0 |
routePath | string | Registered route pattern (e.g. /posts/:id) — deprecated in v4.8.0 |
Notes
matchedRoutesandroutePathare deprecated since v4.8.0; use Route Helper insteadparseBody()requires the content-type to bemultipart/form-dataorapplication/x-www-form-urlencodedcloneRawRequest()is necessary when passing the request downstream after body consumption, sinceRequest.bodyis a one-time-read stream
Related
- context.md
- routing.md
Routing
Hono's routing system supports HTTP method matching, path parameters, optional parameters, regular expression constraints, wildcards, chaining, grouping, and hostname-based routing.
HTTP Methods
app.get('/', (c) => c.text('GET /'))
app.post('/', (c) => c.text('POST /'))
app.put('/post/:id', (c) => c.text('PUT'))
app.delete('/post/:id', (c) => c.text('DELETE'))
app.all('/hello', (c) => c.text('Any method'))
app.on('PURGE', '/cache', (c) => c.text('PURGE'))
app.on(['PUT', 'DELETE'], '/post/:id', (c) => c.text('PUT or DELETE'))Path Parameters
app.get('/user/:name', (c) => {
const name = c.req.param('name')
return c.text(name)
})
app.get('/posts/:id/comment/:commentId', (c) => {
const { id, commentId } = c.req.param()
return c.json({ id, commentId })
})Optional Parameters
Append ? to make a segment optional.
app.get('/api/animal/:type?', (c) => c.text('Animal!'))
// matches both /api/animal and /api/animal/catRegular Expression Constraints
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
const { date, title } = c.req.param()
return c.json({ date, title })
})
app.get('/posts/:filename{.+\\.png}', (c) => c.text('PNG only'))Wildcards
app.get('/wild/*/card', (c) => c.text('Wildcard'))
app.get('*', (c) => c.text('Fallback')) // catch-all; register lastRoutes execute in registration order. A wildcard registered early will prevent later handlers from matching.
Chained Routes
Define multiple HTTP methods on the same path without repeating the path string.
app
.get('/endpoint', (c) => c.text('GET'))
.post((c) => c.text('POST'))
.delete((c) => c.text('DELETE'))Route Grouping
Create separate Hono instances and mount them with app.route().
const book = new Hono()
book.get('/', (c) => c.json([])) // → GET /book
book.get('/:id', (c) => c.json({})) // → GET /book/:id
book.post('/', (c) => c.text('Created', 201))
const app = new Hono()
app.route('/book', book)Base Path
Prefix all routes in an instance with a common path.
const api = new Hono().basePath('/api')
api.get('/book', (c) => c.text('List')) // → GET /api/bookHostname-Based Routing
Override the path extraction function to route by hostname.
const app = new Hono({
getPath: (req) => req.url.replace(/^https?:\/([^?]+).*$/, '$1'),
})
app.get('/www1.example.com/hello', (c) => c.text('hello www1'))
app.get('/www2.example.com/hello', (c) => c.text('hello www2'))Notes
- Handlers and middleware execute in registration order
- First matching route wins; later handlers for the same path are skipped
- Middleware should be registered before route handlers
- Fallback / catch-all handlers should be registered last
Related
- hono.md
- request.md
Benchmarks
Hono publishes benchmark results comparing its routing speed and request handling efficiency against other JavaScript routers and web frameworks across multiple runtimes.
Methodology
- Registers a realistic set of routes (12 total) across each router implementation
- Tests route types: static paths, dynamic routes with parameters, mixed static/dynamic, POST requests, deeply nested routes, and wildcard patterns
- Runs on Node.js, Bun, Cloudflare Workers, and Deno
- Uses standardized load-testing tools (e.g.,
bombardier)
Notes
- Hono claims to be the fastest framework for Cloudflare Workers and Deno environments
- For Bun, Hono is described as one of the fastest frameworks
- Compared implementations include:
@medley/router,find-my-way,koa-tree-router,trek-router,express,koa-router,itty-router,sunder,worktop, and Deno-native frameworks - The
RegExpRouter(single-pass RegExp matching) is the primary driver of Hono's routing performance advantage
Related
- Routers
- Motivation
Developer Experience
Hono is written in TypeScript and prioritizes type-safe application development. Applications can be written for multiple runtimes (Cloudflare Workers, Deno, Bun, Node.js) without transpilation.
Notes
- TypeScript-first: Hono is written in TypeScript; type safety is a built-in feature, not an add-on
- No transpilation required: TypeScript code runs directly on supported runtimes (Deno, Bun, Cloudflare Workers)
- Multi-runtime compatibility: Write once, deploy to any supported runtime without code changes
- Type-safe RPC: When combined with Zod Validator Middleware and the
hcHTTP client (Hono Stacks), server endpoint types are automatically inferred on the client side - Developer experience is considered central to application quality — the framework is designed so that correct usage is easy to express and incorrect usage is caught at compile time
Related
- Stacks
- Motivation
- Web Standards
Middleware
A Handler is a primitive that receives a Request and returns a Response. Middleware is executed before and/or after the Handler, wrapping it in an onion-layer structure to handle cross-cutting concerns.
Signature / Usage
// Registering middleware with app.use()
app.use(async (c, next) => {
const start = Date.now()
await next() // proceed to next middleware / handler
const elapsed = Date.now() - start
c.res.headers.set("X-Response-Time", `${elapsed}ms`)
})Notes
- Middleware is registered via
app.use()and receives aContextobject (c) and anextfunction - The
await next()call passes control to the next layer; code after it runs on the way back out (post-handler) - Follows an onion model: request flows in through each middleware layer, hits the handler, then flows back out in reverse order
- Built-in and third-party middleware is available; custom middleware can be created for any cross-cutting concern (auth, logging, caching, CORS, etc.)
Related
- Routers
- Developer Experience
Motivation
Hono was created to address the lack of suitable web frameworks for Cloudflare Workers and has since evolved into a multi-runtime solution built on Web Standard APIs. Its design goal is to be "damn fast, makes a lot of things possible, and works anywhere."
Notes
- Originally built to fill a gap in frameworks for Cloudflare Workers
- Uses Web Standard APIs instead of runtime-specific solutions for universal compatibility
- Works across Cloudflare Workers, Deno, Bun, Node.js, and other runtimes
- Aims to establish "the Standard for Web Standards" — favoring native web APIs over platform-specific abstractions
- Backed by an ecosystem of middleware and extensions
Related
- Web Standards
- Routers
- Developer Experience
Concepts
| Name | Description | Path |
|---|---|---|
| Benchmarks | Hono publishes benchmark results comparing its routing speed and request handling efficiency… | benchmarks.md |
| Developer Experience | Hono is written in TypeScript and prioritizes type-safe application development. Applications… | developer-experience.md |
| Middleware | A Handler is a primitive that receives a Request and returns a Response. Middleware is executed… | middleware.md |
| Motivation | Hono was created to address the lack of suitable web frameworks for Cloudflare Workers and… | motivation.md |
| Routers | Hono ships multiple router implementations. The default SmartRouter automatically selects… | routers.md |
| Stacks | Hono Stacks is an integrated full-stack pattern combining Hono (API server), Zod (schema… | stacks.md |
| Web Standards | Hono is built exclusively on Web Standards — the standardized APIs originally designed for… | web-standard.md |
Routers
Hono ships multiple router implementations. The default SmartRouter automatically selects the fastest available router for the registered routes at startup.
Router Types
| Router | Algorithm | Best For |
|---|---|---|
RegExpRouter | Compiles all routes into one large RegExp for single-pass matching | General use — fastest in most scenarios |
TrieRouter | Trie-tree traversal, no linear loops | Full pattern coverage where RegExpRouter is unsupported |
SmartRouter | Delegates to the fastest router detected at startup | Default Hono config (wraps RegExpRouter + TrieRouter) |
LinearRouter | Linear scan, no compilation step | Cold-start-sensitive environments (e.g., per-request initialization) |
PatternRouter | Minimal pattern matching | Bundle-size-critical deployments (adds ~5.38 KiB gzipped) |
Notes
- RegExpRouter converts route patterns into a single regular expression, outperforming tree-based algorithms in most cases. It does not support every possible routing pattern.
- TrieRouter handles all routing patterns and is significantly faster than Express, but slower than RegExpRouter.
- SmartRouter evaluates registered routes once at startup and selects the optimal router — Hono's default pairs it with
RegExpRouterandTrieRouter. - LinearRouter skips string compilation entirely, making it ~2.1x faster than alternatives in scenarios where the app is re-initialized per request.
- PatternRouter is the smallest router option; a full Hono app using only PatternRouter fits under 15 KB (5.38 KiB gzipped).
Related
- Benchmarks
- Middleware
Stacks
Hono Stacks is an integrated full-stack pattern combining Hono (API server), Zod (schema validation), Zod Validator Middleware, and the hc HTTP client to enable type-safe, end-to-end application development. "Hono makes easy things easy and hard things easy."
Signature / Usage
// Server: define a type-safe route with Zod validation
import { z } from "zod"
import { zValidator } from "@hono/zod-validator"
const route = app.get(
"/api/posts",
zValidator("query", z.object({ page: z.string() })),
(c) => {
return c.json({ posts: [] })
}
)
export type AppType = typeof route
// Client: infer types automatically via hc
import { hc } from "hono/client"
import type { AppType } from "./server"
const client = hc<AppType>("http://localhost:8787")
const res = await client.api.posts.$get({ query: { page: "1" } })Notes
- The
hcclient infers the full type of each endpoint from the exported app/route type — server-side changes are caught at compile time on the client - All route methods must be chained (not split across statements) for type inference to work correctly
- The endpoint type must be derived from a declared variable, not an inline expression
- Integrates naturally with React Query and other client-side data fetching libraries for full-stack React applications
- Designed for serverless deployments (demonstrated on Cloudflare Pages)
Related
- Developer Experience
- Middleware
Web Standards
Hono is built exclusively on Web Standards — the standardized APIs originally designed for the fetch function. By using native HTTP primitives (Request, Response, URL, URLSearchParams, Headers), the same Hono application code runs across multiple runtimes without modification.
Signature / Usage
// The same handler runs on Cloudflare Workers, Bun, Deno, etc.
export default {
fetch(request: Request): Response {
return new Response("Hello World")
},
}Notes
- Core primitives used:
Request,Response,URL,URLSearchParams,Headers - Supported runtimes: Cloudflare Workers, Deno, Bun, Fastly Compute, AWS Lambda, Node.js, Vercel, Netlify, WASI/WebAssembly
- The WinterCG organization (founded by Cloudflare, Deno, and Shopify) defines the "web-interoperability" standards that Hono follows
- Goal: "the Standard of the Web Standards" — maximum portability with zero runtime-specific code
Related
- Motivation
- Routers
AWS Lambda
Deploy Hono to AWS Lambda (Node.js 18+). Use the hono/aws-lambda adapter and CDK for infrastructure.
Signature / Usage
// lambda/index.ts
import { Hono } from 'hono'
import { handle } from 'hono/aws-lambda'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export const handler = handle(app)Streaming responses:
import { streamHandle } from 'hono/aws-lambda'
export const handler = streamHandle(app)Notes
handle(app)wraps the Hono app as a Lambda handler (request/response mode)streamHandle(app)enables response streaming; requiresinvokeMode: lambda.InvokeMode.RESPONSE_STREAMin CDK- For binary responses, set the
Content-Typeheader; Hono base64-encodes the body automatically - Access
LambdaEventandLambdaContextvia typedc.envbindings - Recommended runtime:
NODEJS_22_X; minimum supported: Node.js 18
Related
- Basic
- Lambda@Edge
Azure Functions
Deploy Hono to Azure Functions V4 (Node.js 18+) via the @marplex/hono-azurefunc-adapter third-party adapter.
Signature / Usage
// src/app.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Azure Functions!'))
export default app// src/functions/httpTrigger.ts
import { app } from '@azure/functions'
import { azureHonoHandler } from '@marplex/hono-azurefunc-adapter'
import honoApp from '../app'
app.http('httpTrigger', {
methods: ['GET', 'POST', 'DELETE', 'PUT'],
authLevel: 'anonymous',
route: '{*proxy}',
handler: azureHonoHandler(honoApp.fetch),
})Notes
- Hono is not officially designed for Azure Functions; integration requires the third-party
@marplex/hono-azurefunc-adapter - The default Azure Functions route prefix is
/api; remove it by setting"routePrefix": ""inhost.jsonunderextensions.http - Initialize with
func init --typescript(Azure Functions Core Tools required) - Dev server:
http://localhost:7071
Related
- Basic
- Node.js
Basic
Core concepts for building Hono applications. The same application code works across all supported runtimes; only the entry point and serve call differ.
Signature / Usage
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
app.get('/api/hello', (c) => c.json({ ok: true, message: 'Hello Hono!' }))
app.get('/posts/:id', (c) => {
const id = c.req.param('id')
const page = c.req.query('page')
return c.text(`post ${id}, page ${page}`)
})
export default appOptions / Props
| Method | Description |
|---|---|
c.text(str) | Return plain text response |
c.json(obj) | Return JSON response |
c.html(str) | Return HTML response |
c.req.param(name) | Get path parameter |
c.req.query(name) | Get query string parameter |
c.header(name, value) | Set response header |
Notes
- The same application code runs on any supported runtime; only the entry point differs
- Built-in middleware includes JWT, Bearer auth, CORS, and ETag
- Use
app.use('/admin/*', middleware)to apply middleware to route groups upgradeWebSocketis available from runtime-specific adapters (e.g.,hono/cloudflare-workers)
Related
- Node.js
- Bun
- Deno
- Cloudflare Workers
Bun
Run Hono natively on Bun. No adapter is required; export the app with an optional port field.
Signature / Usage
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Bun!'))
// Simple export (Bun picks default port)
export default app
// Export with explicit port
export default {
port: 3000,
fetch: app.fetch,
}Notes
- Use
bun run --hot src/index.tsfor hot-reload during development - Static files: import
serveStaticfromhono/bun serveStaticoptions:root,rewriteRequestPath,mimes,onFound,onNotFound,precompressed- Testing: use
bun:test; pass aRequestdirectly toapp.fetch(req)
Related
- Basic
- Service Worker
Cloudflare Pages
Deploy Hono to Cloudflare Pages with Vite and JSX support. Supports bindings (KV, R2, etc.) via wrangler.toml.
Signature / Usage
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.render(<h1>Hello, Cloudflare Pages!</h1>))
export default appBindings:
type Bindings = { MY_NAME: string; MY_KV: KVNamespace }
const app = new Hono<{ Bindings: Bindings }>()
app.get('/', async (c) => {
await c.env.MY_KV.put('name', c.env.MY_NAME)
const name = await c.env.MY_KV.get('name')
return c.render(<h1>Hello! {name}</h1>)
})Pages middleware (`functions/_middleware.ts`):
import { handleMiddleware } from 'hono/cloudflare-pages'
import { basicAuth } from 'hono/basic-auth'
export const onRequest = handleMiddleware(basicAuth({ username: 'hono', password: 'secret' }))Notes
- Dev server runs on
http://localhost:5173(Vite) - Build command for dashboard deployment:
npm run build; build directory:dist - Use
handleMiddlewareto apply Hono middleware to Cloudflare Pages Functions - Multiple middleware:
export const onRequest = [handleMiddleware(m1), handleMiddleware(m2)] - Client-side scripts: toggle between
src/client.ts(dev) and/static/client.js(prod) usingimport.meta.env.PROD
Related
- Basic
- Cloudflare Workers
Cloudflare Workers
Deploy Hono to Cloudflare Workers. The app is the default export; the Worker runtime calls app.fetch automatically.
Signature / Usage
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Cloudflare Workers!'))
export default appModule Worker with additional handlers:
export default {
fetch: app.fetch,
scheduled: async (batch, env) => {},
}Typed bindings:
type Bindings = {
MY_BUCKET: R2Bucket
USERNAME: string
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/config', (c) => c.text(c.env.USERNAME))Notes
- Environment variables are accessed via
c.env, notprocess.env - Local secrets go in
.dev.vars(dotenv syntax); install@cloudflare/workers-typesfor TypeScript - Static assets: set
assets = { directory = "public" }inwrangler.toml - Dev server default port:
8787; deploy withnpm run deploy - CI/CD: store
CLOUDFLARE_API_TOKENas a GitHub Actions secret
Related
- Basic
- Cloudflare Pages
Deno
Run Hono on Deno or deploy to Deno Deploy. Uses Deno.serve() as the entry point.
Signature / Usage
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Deno!'))
Deno.serve(app.fetch)
// or with port:
Deno.serve({ port: 8787 }, app.fetch)Notes
- Initialize with
deno init --npm hono --template=deno my-app - Static files: import
serveStaticfromhono/deno; options includeroot,rewriteRequestPath,mimes,onFound,onNotFound,precompressed - When importing middleware, use the same registry (npm or JSR) throughout to ensure correct TypeScript types
- Testing: use
Deno.testwith@std/assert - Default dev command:
deno task start
Related
- Basic
- Supabase Functions
- Netlify
Fastly Compute
Deploy Hono to Fastly Compute using @fastly/hono-fastly-compute.
Signature / Usage
import { Hono } from 'hono'
import { fire } from '@fastly/hono-fastly-compute'
const app = new Hono()
app.get('/', (c) => c.text('Hello Fastly!'))
fire(app)With Fastly resource bindings (KV Store, Config Store, etc.):
import { buildFire } from '@fastly/hono-fastly-compute'
const fire = buildFire({
siteData: 'KVStore:site-data',
})
const app = new Hono<{ Bindings: typeof fire.Bindings }>()
app.put('/upload/:key', async (c) => {
const key = c.req.param('key')
await c.env.siteData.put(key, c.req.body)
return c.text(`Put ${key} successfully!`)
})
fire(app)Notes
- When using
fire()at the top level, importHonofrom'hono', not'hono/quick';firebuilds the router during initialization - Use
buildFire()instead offire()when you need access to Fastly resources (KV Stores, Config Stores, Secrets) - Resources are accessed via
c.envwith their SDK types - Dev server runs on port
7676; deploy with the Fastly CLI
Related
- Basic
Google Cloud Run
Deploy a Hono app as a containerized service on Google Cloud Run. Any runtime (Node.js, Bun, Deno) is supported via Dockerfile.
Signature / Usage
// src/index.ts — Node.js adapter, port must be 8080
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello!'))
serve({
fetch: app.fetch,
port: 8080,
}, (info) => {
console.log(`Server is running on http://localhost:${info.port}`)
})Notes
- Port must be
8080(Cloud Run requirement) - Default runtime is Node.js if no Dockerfile is provided; provide a Dockerfile for Bun or Deno
- Deploy command:
gcloud run deploy my-app --source . --allow-unauthenticated - Initial deployment may take ~30 seconds before returning a response
- Required APIs:
run.googleapis.com,cloudbuild.googleapis.com
Related
- Basic
- Node.js
Lambda@Edge
Deploy Hono to AWS Lambda@Edge (Node.js 18+). Functions run at CloudFront edge locations.
Signature / Usage
// lambda/index_edge.ts
import { Hono } from 'hono'
import { handle } from 'hono/lambda-edge'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono on Lambda@Edge!'))
export const handler = handle(app)Callback pattern (post-auth request continuation):
app.get('/', async (c, next) => {
await next()
c.env.callback(null, c.env.request)
})Notes
- Import
handlefromhono/lambda-edge(nothono/aws-lambda) - Attach the Lambda function to a CloudFront distribution's
VIEWER_REQUESTevent - Recommended CDK runtime:
NODEJS_20_X - Use CDK to manage CloudFront distributions, IAM roles, and API Gateway automatically
- The
callbackmechanism inc.envallows conditional request continuation after middleware
Related
- Basic
- AWS Lambda
Netlify
Deploy Hono to Netlify Edge Functions. Edge Functions run on Deno and are written in TypeScript.
Signature / Usage
// netlify/edge-functions/index.ts
import { Hono } from 'jsr:@hono/hono'
import { handle } from 'jsr:@hono/hono/netlify'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export default handle(app)Accessing Netlify Context:
import type { Context } from 'https://edge.netlify.com/'
export type Env = { Bindings: { context: Context } }
const app = new Hono<Env>()
app.get('/country', (c) =>
c.json({ 'You are in': c.env.context.geo.country?.name })
)Notes
- Import Hono from
jsr:@hono/hono(JSR registry) for Netlify Edge Functions handle(app)wraps the Hono app for the Edge Functions export format- Netlify Context (geo, etc.) is accessible via
c.env.context - Dev server:
netlify devon port8888; deploy:netlify deploy --prod
Related
- Basic
- Deno
Next.js
Embed Hono inside a Next.js application using catch-all routes. Supports both App Router and Pages Router.
Signature / Usage
App Router (`app/api/[[...route]]/route.ts`):
import { Hono } from 'hono'
import { handle } from 'hono/vercel'
const app = new Hono().basePath('/api')
app.get('/hello', (c) => c.json({ message: 'Hello Next.js!' }))
export const GET = handle(app)
export const POST = handle(app)Pages Router (`pages/api/[[...route]].ts`):
import { Hono } from 'hono'
import { handle } from '@hono/node-server/vercel'
import type { PageConfig } from 'next'
export const config: PageConfig = { api: { bodyParser: false } }
const app = new Hono().basePath('/api')
app.get('/hello', (c) => c.json({ message: 'Hello Next.js!' }))
export default handle(app)Notes
- App Router: import
handlefromhono/vercel - Pages Router: import
handlefrom@hono/node-server/vercel; install@hono/node-server; setbodyParser: false - For Pages Router on Vercel, set the environment variable
NODEJS_HELPERS=0to disable Vercel Node.js helpers - Dev server:
http://localhost:3000
Related
- Basic
- Vercel
- Node.js
Node.js
Run Hono on Node.js using @hono/node-server. Requires Node.js 18.14.1+, 19.7.0+, or 20.0.0+.
Signature / Usage
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Node.js!'))
serve(app)
// or with options:
serve({ fetch: app.fetch, port: 8787 })Options / Props
serve(appOrOptions, callback?)
| Parameter | Type | Description |
|---|---|---|
fetch | Function | The app's fetch handler (app.fetch) |
port | number | Port to listen on (default: 3000) |
createServer | Function | Custom server factory (e.g., http2.createServer) |
serverOptions | object | Options passed to the server factory |
websocket | WebSocketServer | A ws.WebSocketServer instance created with { noServer: true } for WebSocket support |
Notes
- Access Node.js-specific APIs via
c.env.incoming(the rawIncomingMessage) - Serve static files with
serveStaticfrom@hono/node-server/serve-static; preferimport.meta.urlfor reliable path resolution - HTTP/2 is supported by passing
createServer/createSecureServerfromnode:http2 - Graceful shutdown:
serve()returns a server instance; callserver.close()onSIGINT/SIGTERM - Default dev port:
3000 - WebSocket:
@hono/node-wsis deprecated. UseupgradeWebSocketfrom@hono/node-serverwith thewebsocketserve option instead (requireswspackage)
Related
- Basic
- Google Cloud Run
- Next.js
Getting Started
| Name | Description | Path |
|---|---|---|
| AWS Lambda | Deploy Hono to AWS Lambda (Node.js 18+). Use the hono/aws-lambda adapter and CDK… | aws-lambda.md |
| Azure Functions | Deploy Hono to Azure Functions V4 (Node.js 18+) via the @marplex/hono-azurefunc-adapter… | azure-functions.md |
| Basic | Core concepts for building Hono applications. The same application code works across… | basic.md |
| Bun | Run Hono natively on Bun. No adapter is required; export the app with an optional… | bun.md |
| Cloudflare Pages | Deploy Hono to Cloudflare Pages with Vite and JSX support. Supports bindings (KV, R2… | cloudflare-pages.md |
| Cloudflare Workers | Deploy Hono to Cloudflare Workers. The app is the default export; the Worker runtime… | cloudflare-workers.md |
| Deno | Run Hono on Deno or deploy to Deno Deploy. Uses Deno.serve() as the entry… | deno.md |
| Fastly Compute | Deploy Hono to Fastly Compute using @fastly/hono-fastly-compute. | fastly.md |
| Google Cloud Run | Deploy a Hono app as a containerized service on Google Cloud Run. Any runtime (Node.js… | google-cloud-run.md |
| Lambda@Edge | Deploy Hono to AWS Lambda@Edge (Node.js 18+). Functions run at CloudFront edge… | lambda-edge.md |
| Netlify | Deploy Hono to Netlify Edge Functions. Edge Functions run on Deno and are written in… | netlify.md |
| Next.js | Embed Hono inside a Next.js application using catch-all routes. Supports both App… | nextjs.md |
| Node.js | Run Hono on Node.js using @hono/node-server. Requires Node.js 18.14.1+, 19.7.0+… | nodejs.md |
| Service Worker | Run Hono inside a browser Service Worker using the hono/service-worker adapter. | service-worker.md |
| Supabase Functions | Deploy Hono to Supabase Edge Functions, which run on Deno. | supabase-functions.md |
| Vercel | Deploy Hono to Vercel with zero configuration. Use the hono/vercel adapter to… | vercel.md |
Service Worker
Run Hono inside a browser Service Worker using the hono/service-worker adapter.
Signature / Usage
// sw.ts
import { Hono } from 'hono'
import { handle } from 'hono/service-worker'
const app = new Hono().basePath('/sw')
app.get('/', (c) => c.text('Hello World'))
self.addEventListener('fetch', handle(app))Alternative — `fire()` shorthand:
import { fire } from 'hono/service-worker'
fire(app)Service Worker registration (`main.ts`):
navigator.serviceWorker.register('/sw.ts', { scope: '/sw', type: 'module' })Notes
tsconfig.jsonmust include"lib": ["ES2020", "DOM", "WebWorker"]- Service Workers run in the background of the browser; they intercept
fetchevents within their registered scope - The
basePathof the Hono app must match the Service Worker'sscope - Build with Vite; dev server default port:
5173 - Access the app at
http://localhost:5173/, then navigate to/swfor Hono responses
Related
- Basic
Supabase Functions
Deploy Hono to Supabase Edge Functions, which run on Deno.
Signature / Usage
// supabase/functions/hello-world/index.ts
import { Hono } from 'jsr:@hono/hono'
const functionName = 'hello-world'
const app = new Hono().basePath(`/${functionName}`)
app.get('/hello', (c) => c.text('Hello from hono-server!'))
Deno.serve(app.fetch)Notes
- Import Hono from
jsr:@hono/hono(JSR registry) - The
basePathmust match the Supabase function name (e.g.,/hello-world) - Local dev:
supabase startthensupabase functions serve --no-verify-jwt; default URLhttp://127.0.0.1:54321/functions/v1/{functionName} - The
--no-verify-jwtflag is required for local testing without JWT auth - Deploy:
supabase functions deployorsupabase functions deploy hello-world - Edge Functions run on Deno for improved security and modern JS/TS support
Related
- Basic
- Deno
Vercel
Deploy Hono to Vercel with zero configuration. Use the hono/vercel adapter to export route handlers.
Signature / Usage
import { Hono } from 'hono'
import { handle } from 'hono/vercel'
const app = new Hono().basePath('/api')
app.get('/hello', (c) => c.json({ message: 'Hello Next.js!' }))
export const GET = handle(app)
export const POST = handle(app)Notes
- Initialize with
npm create hono@latest my-appand select theverceltemplate - Development:
vercel dev(port3000); production:vercel deploy - Zero-configuration deployment — no extra Vercel config needed
- For standalone Vercel (not Next.js), export
default app; Hono handles routing automatically
Related
- Basic
- Next.js
Best Practices
Recommended patterns for building maintainable, type-safe Hono applications.
Don't Separate Handlers into Controller Files
Avoid Rails-style controllers that separate handlers from route definitions. Path parameter types cannot be inferred without complex generics.
// Not recommended
const bookListHandler = (c: Context) => {
return c.json('list books')
}
app.get('/books', bookListHandler)Inline Handler (Recommended)
Define handlers inline with route definitions to preserve TypeScript type inference.
app.get('/books/:id', (c) => {
const id = c.req.param('id') // correctly typed
return c.json(`get ${id}`)
})Factory Pattern for Controller-Style Organization
Use createFactory() from hono/factory when you want to separate handlers while retaining type safety and middleware composition.
import { createFactory } from 'hono/factory'
const factory = createFactory()
const middleware = factory.createMiddleware(async (c, next) => {
c.set('foo', 'bar')
await next()
})
const handlers = factory.createHandlers(logger(), middleware, (c) => {
return c.json(c.var.foo)
})
app.get('/api', ...handlers)Modular Applications with app.route()
Split large apps into sub-applications mounted at specific paths.
// books.ts
const app = new Hono()
app.get('/', (c) => c.json('list books'))
app.post('/', (c) => c.json('created', 201))
export default app// index.ts
import books from './books'
import authors from './authors'
app.route('/books', books)
app.route('/authors', authors)RPC-Compatible Route Chaining
Chain route definitions and export the app type for full end-to-end type inference with the RPC client.
const app = new Hono()
.get('/', (c) => c.json('list'))
.post('/', (c) => c.json('created', 201))
.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export type AppType = typeof appNotes
- Inline handlers are the simplest path to correct type inference for path parameters.
createFactory()is the escape hatch when file organization requires separation.- Chaining +
export type AppTypeis required for RPC type sharing.
Related
- rpc.md
- middleware.md
Examples
Reference implementations and integration examples. All source code is in the honojs/examples repository.
Application Examples
| Name | Description |
|---|---|
| Web API | Basic web service / REST API implementation |
| Proxy | Proxy server setup |
| File upload | Handling file submissions |
| Bind a reverse proxy | Configuration behind reverse proxies |
| Error handling in Validator | Managing validation errors gracefully |
| Grouping routes for RPC | Organizing routes for type-safe RPC |
| CBOR | CBOR data format serialization |
3rd-Party Middleware Examples
| Name | Description |
|---|---|
| Zod OpenAPI | Schema validation combined with OpenAPI spec generation |
| Hono OpenAPI | Native OpenAPI support |
| Swagger UI | Interactive API documentation UI |
| Scalar | Alternative API documentation tool |
| Hono Docs Generator | Automated documentation generation |
Integration Examples
| Name | Category |
|---|---|
| Cloudflare Durable Objects | Platform |
| Cloudflare Queue | Platform |
| Cloudflare Workers Testing | Platform |
| Better Auth | Authentication |
| Auth.js | Authentication |
| Stytch | Authentication |
| Remix | Frontend |
| htmx | Frontend |
| Prisma | Database |
| Pylon (GraphQL) | API |
| Stripe webhooks | Payments |
| Apitally | API monitoring |
Related
- others.md
JSX DOM (Client-Side)
Client-side interactive UIs using hono/jsx/dom. The bundle is only ~2.8KB (Brotli) vs React's ~47.8KB.
Configuration
tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx/dom"
}
}Vite (vite.config.ts)
import { defineConfig } from 'vite'
export default defineConfig({
esbuild: {
jsxImportSource: 'hono/jsx/dom',
},
})Using hono/jsx/dom (not hono/jsx) produces smaller bundles.
Signature / Usage
import { useState } from 'hono/jsx'
import { render } from 'hono/jsx/dom'
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
const root = document.getElementById('root')
render(<Counter />, root)Available Hooks
| Hook | Description |
|---|---|
useState | Local state management |
useReducer | Reducer-based state |
useEffect | Side effects after render |
useLayoutEffect | Side effects before paint |
useInsertionEffect | DOM injection before layout |
useRef / createRef | Mutable refs |
forwardRef / useImperativeHandle | Ref forwarding |
useCallback | Memoize callbacks |
useMemo | Memoize computed values |
useDeferredValue | Defer non-urgent updates |
useTransition / startTransition | Mark updates as non-urgent |
useViewTransition | View Transitions API |
useFormStatus | Form submission state |
useActionState | Form action state |
useOptimistic | Optimistic UI updates |
use | Read resources / context |
useId | Stable unique IDs |
useSyncExternalStore | Subscribe to external stores |
memo | Memoize components |
createElement | Create elements imperatively |
isValidElement | Type guard for JSX elements |
View Transitions API
Basic Transition with startViewTransition
import { useState, startViewTransition } from 'hono/jsx'
export default function App() {
const [showLarge, setShowLarge] = useState(false)
return (
<button onClick={() => startViewTransition(() => setShowLarge((s) => !s))}>
Toggle
</button>
)
}With useViewTransition Hook
useViewTransition() returns [isUpdating, startViewTransition]. isUpdating is true during the transition animation, enabling style changes.
const [isUpdating, startViewTransition] = useViewTransition()With Keyframe Animations
import { viewTransition } from 'hono/jsx/dom/css'
import { css, keyframes, Style } from 'hono/css'
const rotate = keyframes`
from { rotate: 0deg; }
to { rotate: 360deg; }
`
const [transitionNameClass] = useState(() =>
viewTransition(css`
::view-transition-old() { animation-name: ${rotate}; }
::view-transition-new() { animation-name: ${rotate}; }
`)
)Notes
- The hook API is compatible with React's — same names and semantics.
- Use
hono/jsx/domasjsxImportSource(nothono/jsx) for the smaller client runtime.
Related
- jsx.md
JSX (Server-Side)
Server-side JSX rendering for Hono applications using hono/jsx.
Configuration
tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}Or use per-file pragma directives:
/** @jsxImportSource hono/jsx */Deno (deno.json)
{
"compilerOptions": {
"jsx": "precompile",
"jsxImportSource": "@hono/hono/jsx"
}
}Files must use the .tsx extension.
Signature / Usage
import { Hono } from 'hono'
import type { FC } from 'hono/jsx'
const app = new Hono()
const Layout: FC = (props) => (
<html>
<body>{props.children}</body>
</html>
)
const Top: FC<{ messages: string[] }> = (props) => (
<Layout>
<h1>Hello Hono!</h1>
<ul>
{props.messages.map((message) => <li>{message}</li>)}
</ul>
</Layout>
)
app.get('/', (c) => {
const messages = ['Good Morning', 'Good Evening', 'Good Night']
return c.html(<Top messages={messages} />)
})Features
Fragments
import { Fragment } from 'hono/jsx'
const List = () => (
<>
<p>first child</p>
<p>second child</p>
</>
)PropsWithChildren
import { PropsWithChildren } from 'hono/jsx'
function Component({ title, children }: PropsWithChildren<{ title: string }>) {
return <div><h1>{title}</h1>{children}</div>
}Raw HTML
const inner = { __html: 'JSX · SSR' }
return c.html(<div dangerouslySetInnerHTML={inner} />)Memoization
import { memo } from 'hono/jsx'
const Header = memo(() => <header>Welcome to Hono</header>)Context API
import { createContext, useContext } from 'hono/jsx'
const ThemeContext = createContext(themes.light)
const Button: FC = () => {
const theme = useContext(ThemeContext)
return <button style={theme}>Push!</button>
}Async Components
const AsyncComponent = async () => {
await someAsyncWork()
return <div>Done!</div>
}Streaming with Suspense (Experimental)
import { renderToReadableStream, Suspense } from 'hono/jsx/streaming'
app.get('/', (c) => {
const stream = renderToReadableStream(
<html>
<body>
<Suspense fallback={<div>loading...</div>}>
<AsyncComponent />
</Suspense>
</body>
</html>
)
return c.body(stream, {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
'Transfer-Encoding': 'chunked',
},
})
})Metadata Hoisting
<title>, <meta>, and <link> tags are automatically hoisted to <head> when using c.render().
app.get('/about', (c) => {
return c.render(
<>
<title>About Page</title>
<meta name='description' content='This is the about page.' />
<p>content here</p>
</>
)
})Custom Elements
declare module 'hono/jsx' {
namespace JSX {
interface IntrinsicElements {
'my-custom-element': HTMLAttributes & {
'x-event'?: 'click' | 'scroll'
}
}
}
}Notes
- Use
.tsxfile extension (not.ts) or JSX will fail at runtime. SuspenseandErrorBoundaryare experimental features.- For CSP nonce support with streaming, use
StreamingContextfromhono/jsx/streaming.
Related
- jsx-dom.md
Middleware
How to write custom middleware in Hono. Middleware runs before and after handlers in a nested stack.
Signature / Usage
Middleware must either call await next() and return nothing (to continue the chain), or return a Response to short-circuit.
app.use(async (c, next) => {
// before handler
await next()
// after handler
})Inline Middleware
Define middleware directly in app.use() for one-off cases.
app.use('/message/*', async (c, next) => {
await next()
c.header('x-message', 'This is middleware!')
})Reusable Middleware with createMiddleware()
Use createMiddleware() from hono/factory to create typed, reusable middleware.
import { createMiddleware } from 'hono/factory'
const logger = createMiddleware(async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`)
await next()
})Extending Context Variables
Pass typed data downstream using c.set(). Declare the variable type as a generic on createMiddleware.
import { createMiddleware } from 'hono/factory'
const echoMiddleware = createMiddleware<{
Variables: {
echo: (str: string) => string
}
}>(async (c, next) => {
c.set('echo', (str) => str)
await next()
})
app.get('/echo', echoMiddleware, (c) => {
return c.text(c.var.echo('Hello!'))
})Execution Order
Middleware executes in a nested stack. Code before next() runs on the way in; code after next() runs on the way out.
middleware 1 start
middleware 2 start
handler
middleware 2 end
middleware 1 endType Accumulation via Chaining
When chaining .use() calls, Hono accumulates Variables types automatically so all downstream handlers have access.
const app = new Hono()
.use(authMiddleware)
.use(dbMiddleware)
.get('/', (c) => {
const user = c.var.user // typed
const db = c.var.db // typed
return c.json({ user })
})Notes
- Do not wrap
next()in try/catch — Hono catches errors from handlers and middleware automatically. - Return a
Responsefrom middleware to short-circuit (e.g., for auth checks) without callingnext().
Related
- best-practices.md
- testing.md
Others
Contributing, sponsoring, and key resources for the Hono project.
Contributing
Ways to contribute:
- Open an Issue to propose features or report bugs
- Submit pull requests (bug fixes, typo corrections, refactoring)
- Develop third-party middleware
- Share feedback on blogs or social media
- Build applications with Hono
Sponsoring
| Maintainer | Link |
|---|---|
| Yusukebe | GitHub Sponsors |
| Usualoma | GitHub Sponsors |
Resources
| Resource | URL |
|---|---|
| GitHub | https://github.com/honojs |
| npm | https://www.npmjs.com/package/hono |
| JSR | https://jsr.io/@hono/hono |
Guides
| Name | Description | Path |
|---|---|---|
| Best Practices | Recommended patterns for building maintainable, type-safe Hono applications. | best-practices.md |
| Examples | Reference implementations and integration examples. All source code is in… | examples.md |
| JSX (Server-Side) | Server-side JSX rendering for Hono applications using hono/jsx. | jsx.md |
| JSX DOM (Client-Side) | Client-side interactive UIs using hono/jsx/dom. The bundle is only ~2… | jsx-dom.md |
| Middleware | How to write custom middleware in Hono. Middleware runs before and after… | middleware.md |
| Others | Contributing, sponsoring, and key resources for the Hono project. | others.md |
| RPC | Type-safe client-server communication by sharing API specifications… | rpc.md |
| Testing | Testing Hono applications using app.request() and the typed test… | testing.md |
| Validation | Input validation for Hono routes using built-in validators and… | validation.md |
RPC
Type-safe client-server communication by sharing API specifications through TypeScript types.
Server Setup
Define routes with validators, chain the definitions, and export the app type.
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import * as z from 'zod'
const app = new Hono()
.post(
'/posts',
zValidator('form', z.object({ title: z.string(), body: z.string() })),
(c) => {
return c.json({ ok: true, message: 'Created!' }, 201)
}
)
export type AppType = typeof appClient Setup
import type { AppType } from './server'
import { hc } from 'hono/client'
const client = hc<AppType>('http://localhost:8787/')
const res = await client.posts.$post({
form: { title: 'Hello', body: 'Hono is cool' },
})
if (res.ok) {
const data = await res.json() // typed
}Path Parameters and Query Strings
Path params and query values must be passed as strings.
const res = await client.posts[':id'].$get({
param: { id: '123' },
query: { page: '1' },
})Custom Headers and Fetch Options
const res = await client.search.$get(
{ query: { q: 'hono' } },
{ headers: { 'X-Custom-Header': 'value' } }
)Utility Types
| Export | Description |
|---|---|
InferRequestType | Extract request type from a client method |
InferResponseType | Extract response type from a client method |
parseResponse() | Type-safe response parsing with Content-Type handling |
ApplyGlobalResponse | Merge global error handler response types into all routes |
import { InferRequestType, InferResponseType } from 'hono/client'
type PostRequest = InferRequestType<typeof client.posts.$post>
type PostResponse = InferResponseType<typeof client.posts.$post, 201>ApplyGlobalResponse
The RPC client does not automatically infer response types from global error handlers (app.onError). ApplyGlobalResponse merges those error types into all routes.
import type { ApplyGlobalResponse } from 'hono/client'
type AppType = ApplyGlobalResponse<typeof app, { 500: { json: { message: string } } }>
const client = hc<AppType>('http://localhost:8787/')Custom Fetch and Query Serialization
// Custom fetch (e.g., Cloudflare Service Bindings)
const client = hc<AppType>('/', { fetch: env.ANOTHER_SERVICE.fetch.bind(env.ANOTHER_SERVICE) })
// Custom query serialization (e.g., bracket notation for arrays)
const client = hc<AppType>('/', {
buildSearchParams(record) {
const params = new URLSearchParams()
for (const [k, v] of Object.entries(record)) {
if (Array.isArray(v)) v.forEach((val) => params.append(`${k}[]`, val))
else params.set(k, String(v))
}
return params
},
})URL Helpers
client.posts.$url() // URL object (requires absolute base URL)
client.posts.$path() // path string onlyFile Uploads
const res = await client.user.picture.$put({
form: { file: new File([blob], 'photo.jpg', { type: 'image/jpeg' }) },
})Notes
- Set
"strict": trueintsconfig.jsonon both server and client (critical in monorepos). - Use
c.json({ ... }, 404)instead ofc.notFound()—notFound()breaks type inference. - Status codes explicitly specified in handlers are part of the type system; use them for precise response handling.
- Large apps may slow down the IDE. Mitigations: compile types at build time, use TypeScript project references, or split the app.
- Route chaining (
.get().post()...) on a singleappinstance is required for RPC types to work correctly.
Related
- best-practices.md
- validation.md
- testing.md
Testing
Testing Hono applications using app.request() and the typed test client.
Core Method: app.request()
Pass a path (or full Request) to app.request() and assert on the returned Response. No HTTP server is needed.
import { describe, expect, test } from 'vitest'
import app from './app'
test('GET /posts', async () => {
const res = await app.request('/posts')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Many posts')
})POST with JSON Body
test('POST /posts with JSON', async () => {
const res = await app.request('/posts', {
method: 'POST',
body: JSON.stringify({ message: 'hello hono' }),
headers: new Headers({ 'Content-Type': 'application/json' }),
})
expect(res.status).toBe(201)
})POST with FormData
test('POST /posts with FormData', async () => {
const formData = new FormData()
formData.append('message', 'hello')
const res = await app.request('/posts', {
method: 'POST',
body: formData,
})
expect(res.status).toBe(201)
})Using a Request Instance
test('POST /posts', async () => {
const req = new Request('http://localhost/posts', { method: 'POST' })
const res = await app.request(req)
expect(res.status).toBe(201)
})Mocking Environment Variables
Pass mock bindings (Cloudflare Workers env, D1, KV, etc.) as the third argument.
const MOCK_ENV = {
API_HOST: 'example.com',
DB: {
prepare: () => ({ /* mocked D1 */ }),
},
}
test('GET /posts with env', async () => {
const res = await app.request('/posts', {}, MOCK_ENV)
expect(res.status).toBe(200)
})Typed Test Client
Use the RPC hc client with the exported app type for end-to-end type-safe tests. See the testing helper documentation for details.
Notes
- Always set
Content-Type: application/jsonwhen sending JSON; without it Hono cannot parse the body. - For Cloudflare Workers, the recommended test runner is Vitest with
@cloudflare/vitest-pool-workers. app.request()accepts either a path string or a fullRequestobject.
Related
- rpc.md
- validation.md
Validation
Input validation for Hono routes using built-in validators and third-party integrations.
Signature / Usage
import { validator } from 'hono/validator'
app.post(
'/posts',
validator('form', (value, c) => {
const body = value['body']
if (!body || typeof body !== 'string') {
return c.text('Invalid!', 400)
}
return { body }
}),
(c) => {
const { body } = c.req.valid('form')
return c.json({ message: 'Created!' }, 201)
}
)Validation Targets
| Target | Description |
|---|---|
'form' | application/x-www-form-urlencoded or multipart/form-data |
'json' | application/json request body |
'query' | URL query string parameters |
'header' | Request headers |
'param' | Path parameters |
'cookie' | Cookie values |
Multiple Validators
Chain validators for different parts of the same request.
app.post(
'/posts/:id',
validator('param', ...),
validator('query', ...),
validator('json', ...),
(c) => { /* all validated data available via c.req.valid() */ }
)Zod Integration (Manual)
import * as z from 'zod'
const schema = z.object({ body: z.string() })
app.post(
'/posts',
validator('form', (value, c) => {
const parsed = schema.safeParse(value)
if (!parsed.success) {
return c.text('Invalid!', 400)
}
return parsed.data
}),
(c) => { /* handler */ }
)Zod Validator Middleware
Install @hono/zod-validator for a simpler API.
import { zValidator } from '@hono/zod-validator'
import * as z from 'zod'
app.post(
'/posts',
zValidator('form', z.object({ body: z.string() })),
(c) => {
const { body } = c.req.valid('form')
return c.json({ message: 'Created!' }, 201)
}
)Standard Schema Validator Middleware
@hono/standard-validator supports any Standard Schema-compatible library (Zod, Valibot, ArkType).
import { sValidator } from '@hono/standard-validator'
import * as z from 'zod'
const schema = z.object({ name: z.string(), age: z.number() })
app.post('/author', sValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json({ success: true, message: `${data.name} is ${data.age}` })
})Notes
jsonandformvalidators require the correctContent-Typeheader on the request; without it the body is empty.- Header names must be lowercase when used as validation keys (e.g.,
'idempotency-key', not'Idempotency-Key'). - Validated data is accessed via
c.req.valid(target)in the handler — notc.req.json()orc.req.formData().
Related
- rpc.md
- testing.md
Accepts Helper
HTTP content negotiation — matches client Accept-* headers against server-supported values.
Signature / Usage
import { accepts } from 'hono/accepts'
app.get('/', (c) => {
const lang = accepts(c, {
header: 'Accept-Language',
supports: ['en', 'ja', 'zh'],
default: 'en',
})
return c.text(`Language: ${lang}`)
})Options / Props
| Name | Type | Description |
|---|---|---|
header | AcceptHeader | Target header to inspect (see below) |
supports | string[] | Values the server can provide |
default | string | Fallback when no match is found |
match | (accepts: Accept[], config: acceptsConfig) => string (optional) | Custom matching function |
AcceptHeader values
Accept, Accept-Charset, Accept-Encoding, Accept-Language, Accept-Patch, Accept-Post, Accept-Ranges
Notes
- Returns the best-matching value from
supportsbased on client quality factors (q=) - Falls back to
defaultwhen no supported value matches the client preference
Related
- Adapter Helper
Adapter Helper
Runtime-agnostic utilities for accessing environment variables and identifying the current runtime.
Signature / Usage
import { env, getRuntimeKey } from 'hono/adapter'
// Retrieve env vars regardless of runtime
app.get('/', (c) => {
const { NAME } = env<{ NAME: string }>(c)
return c.text(NAME)
})
// Branch on runtime
if (getRuntimeKey() === 'workerd') {
// Cloudflare Workers specific logic
}Options / Props
env(c, runtime?)
| Parameter | Type | Description |
|---|---|---|
c | Context | Hono context |
runtime | string (optional) | Force a specific runtime key (e.g., 'workerd') |
Returns: generic T — the environment bindings object.
getRuntimeKey()
Returns: string — one of workerd, deno, bun, node, edge-light, fastly, other.
Notes
- The shape of the value returned by
env(c)differs per runtime (e.g.,process.envon Node,c.envon Cloudflare Workers) - Supported platforms: Cloudflare Workers, Deno, Bun, Node.js, Vercel, AWS Lambda, Lambda@Edge, Fastly Compute, Netlify
Related
- Proxy Helper
- ConnInfo Helper
ConnInfo Helper
Retrieve TCP/UDP connection metadata (remote address, transport, port) from the current request.
Signature / Usage
// Import path varies by platform:
import { getConnInfo } from 'hono/cloudflare-workers' // Cloudflare Workers
import { getConnInfo } from 'hono/deno' // Deno
import { getConnInfo } from '@hono/node-server/conninfo' // Node.js
// Also available for Bun, Vercel, AWS Lambda, etc.
app.get('/', (c) => {
const info = getConnInfo(c)
return c.text(`Your IP: ${info.remote.address}`)
})Options / Props
getConnInfo(c)
| Parameter | Type | Description |
|---|---|---|
c | Context | Hono context |
Returns: ConnInfo
ConnInfo structure
| Field | Type | Description |
|---|---|---|
remote.address | string | Client host name or IP address |
remote.addressType | `'IPv4' \ | 'IPv6' \ |
remote.transport | `'tcp' \ | 'udp' \ |
remote.port | `number \ | undefined` |
Notes
- The import path is platform-specific; use the adapter matching your runtime
- Node.js requires
@hono/node-server/conninfo(separate package)
Related
- Adapter Helper
Cookie Helper
Read, write, and delete HTTP cookies with optional cookie prefixes.
Signature / Usage
import {
getCookie, setCookie, deleteCookie,
getSignedCookie, setSignedCookie,
generateCookie, generateSignedCookie,
} from 'hono/cookie'
app.get('/', (c) => {
const all = getCookie(c) // all cookies
const value = getCookie(c, 'name') // single cookie
const secure = getCookie(c, 'name', 'secure') // with prefix
setCookie(c, 'session', 'abc123', {
path: '/',
httpOnly: true,
secure: true,
maxAge: 3600,
sameSite: 'Lax',
})
const deleted = deleteCookie(c, 'session', { path: '/' })
})Options / Props
getCookie(c, name?, prefix?)
| Parameter | Type | Description |
|---|---|---|
c | Context | Hono context |
name | string (optional) | Cookie name; omit to get all cookies |
prefix | `'secure' \ | 'host'` (optional) |
Returns: string (single cookie) or Record<string, string> (all cookies).
setCookie(c, name, value, options?)
| Option | Type | Description |
|---|---|---|
domain | string | Cookie domain |
expires | Date | Expiry date |
httpOnly | boolean | HTTP-only flag |
maxAge | number | Max age in seconds (must be ≤ 400 days) |
path | string | Cookie path |
secure | boolean | Secure flag |
sameSite | `'Strict' \ | 'Lax' \ |
priority | `'Low' \ | 'Medium' \ |
prefix | `'secure' \ | 'host'` |
partitioned | boolean | Partitioned (CHIPS) attribute |
deleteCookie(c, name, options?)
Options: path, secure, domain. Returns the deleted cookie value.
getSignedCookie(c, secret, name?, prefix?) / setSignedCookie(c, name, value, secret, options?)
Signed cookies use HMAC SHA-256. getSignedCookie returns false if the signature was tampered with.
await setSignedCookie(c, 'user', 'alice', 'mysecret', { httpOnly: true })
const user = await getSignedCookie(c, 'mysecret', 'user')
// returns false if tamperedgenerateCookie(name, value, options?) / generateSignedCookie(name, value, secret, options?)
Generate cookie strings without setting them in response headers. Useful for constructing Set-Cookie headers manually.
const cookieStr = generateCookie('session', 'abc123', { httpOnly: true })
const signedStr = await generateSignedCookie('session', 'abc123', 'secret')Notes
maxAgevalues greater than 400 days will throw an error per RFC best practicessameSiteandpriorityoptions are validated against their allowed values; invalid values throw aTypeError- Cookie prefix rules:
__Secure-requiressecure: true;__Host-requiressecure: true,path: '/', and nodomain
Related
- JWT Helper
CSS Helper
Write CSS-in-JS using tagged template literals inside JSX. Requires <Style /> in the document head.
Signature / Usage
import { css, cx, keyframes, Style } from 'hono/css'
// Generate a class name
const headerClass = css`
background-color: orange;
color: white;
&:hover { opacity: 0.8; }
`
// Define keyframe animation
const fadeIn = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`
// Compose multiple classes
const combined = cx(headerClass, extraClass)
// In JSX render
<head><Style /></head>
<h1 class={headerClass}>Hello</h1>Options / Props
| Name | Type | Description |
|---|---|---|
css | tagged template | Generates a scoped class name string |
keyframes | tagged template | Defines a CSS animation, returns animation name string |
cx | (...classes: string[]) => string | Composes multiple class names |
Style | JSX component | Injects collected CSS into <style> tag; accepts nonce prop |
Notes
<Style />must be included in the document<head>or the CSS will not render- Pseudo-classes use the nesting selector
&(e.g.,&:hover) - Global styles can be defined using the
:-hono-globalpseudo-selector nonceprop on<Style />supports Content-Security-Policy headers
Related
- HTML Helper
Dev Helper
Utilities for inspecting registered routes and the active router during development.
Signature / Usage
import { showRoutes, getRouterName } from 'hono/dev'
const app = new Hono().basePath('/v1')
app.get('/posts', (c) => c.text('list'))
app.get('/posts/:id', (c) => c.text('show'))
app.post('/posts', (c) => c.text('create'))
showRoutes(app, { verbose: true })
// GET /v1/posts
// GET /v1/posts/:id
// POST /v1/posts
console.log(getRouterName(app))
// e.g. 'SmartRouter'Options / Props
showRoutes(app, options?)
| Parameter | Type | Description |
|---|---|---|
app | Hono | Application instance |
options.verbose | boolean | Show additional details when true |
options.colorize | boolean | Disable colored output when false |
getRouterName(app)
| Parameter | Type | Description |
|---|---|---|
app | Hono | Application instance |
Returns: string — name of the active router (e.g., 'SmartRouter', 'RegExpRouter').
Notes
- These helpers are intended for development/debugging only; avoid using in production handlers
Related
- Route Helper
- SSG Helper
Factory Helper
Create typed middleware and handlers outside of route definitions, sharing a common Env type.
Signature / Usage
import { createFactory, createMiddleware } from 'hono/factory'
// Standalone middleware (no factory required)
const logger = createMiddleware(async (c, next) => {
console.log(c.req.url)
await next()
})
// Parameterized middleware factory pattern
const withMessage = (msg: string) =>
createMiddleware(async (c, next) => {
await next()
c.res.headers.set('X-Message', msg)
})
// Factory with shared Env type
type Env = { Variables: { user: User } }
const factory = createFactory<Env>({
initApp: (app) => { /* configure app */ },
})
const handlers = factory.createHandlers(
logger,
async (c) => c.json({ ok: true })
)
app.get('/route', ...handlers)Options / Props
createFactory<Env>(options?)
| Option | Type | Description |
|---|---|---|
defaultAppOptions | HonoOptions | Default options passed to created Hono instances |
initApp | (app: Hono) => void | Callback to initialize each created app |
Returns a factory instance with createHandlers() and createApp() methods.
createMiddleware<Env>(handler)
| Parameter | Type | Description |
|---|---|---|
handler | (c: Context, next: Next) => Promise<void> | Middleware implementation |
Returns: typed middleware function.
factory.createHandlers(...middleware, handler)
Accepts any number of middleware followed by a final handler. Returns an array that can be spread into route definitions.
Notes
- Use
createFactory<Env>()to avoid repeating theEnvgeneric on every middleware and app declaration createHandlershelps define handlers in a separate file from route registration
Related
- Route Helper
- Testing Helper
HTML Helper
Tagged template literal for writing HTML safely in JavaScript, plus raw() for unescaped output.
Signature / Usage
import { html, raw } from 'hono/html'
// Tagged template literal
html`<h1>Hello! ${username}!</h1>`
// Render unescaped content
raw(content: string): HtmlEscapedString
// As a functional component
const Footer = () => html`<footer><address>...</address></footer>`Notes
- Variables interpolated into
htmlare automatically escaped; useraw()to bypass escaping (manual sanitization required) - Can be used as a JSX replacement — no need for
dangerouslySetInnerHTML - IDE syntax highlighting via lit-html extensions
Related
- CSS Helper
- Streaming Helper
JWT Helper
Sign, verify, and decode JSON Web Tokens (HS256 by default).
Signature / Usage
import { sign, verify, decode } from 'hono/jwt'
// Sign
const token = await sign({ sub: 'user123', exp: Math.floor(Date.now() / 1000) + 300 }, 'secret')
// Verify (throws on invalid/expired token)
const payload = await verify(token, 'secret', 'HS256')
// Decode without verification
const { header, payload } = decode(token)Options / Props
sign(payload, secret, alg?)
| Parameter | Type | Description |
|---|---|---|
payload | unknown | Data to encode |
secret | string | Signing key |
alg | string (optional) | Algorithm; defaults to 'HS256' |
Returns: Promise<string> — signed JWT.
verify(token, secret, alg, issuer?)
| Parameter | Type | Description |
|---|---|---|
token | string | JWT to validate |
secret | string | Verification key |
alg | string | Algorithm used when signing |
issuer | `string \ | RegExp` (optional) |
Returns: Promise<any> — decoded payload if valid; throws otherwise.
Validates: exp, nbf, iat, iss.
decode(token)
| Parameter | Type | Description |
|---|---|---|
token | string | JWT to decode |
Returns: { header: any; payload: any } — decoded without signature verification.
Notes
decodedoes not verify the signature; use only for inspection/debugging- For route-level auth, prefer the
hono/jwtmiddleware over callingverifymanually
Related
- Cookie Helper
Proxy Helper
Forward incoming requests to an upstream origin with automatic header sanitization.
Signature / Usage
import { proxy } from 'hono/proxy'
app.get('/proxy/:path', (c) =>
proxy(`http://origin/${c.req.param('path')}`)
)
// With options
app.get('/proxy/:path', (c) =>
proxy(`http://origin/${c.req.param('path')}`, {
headers: { Authorization: undefined }, // exclude header
})
)Options / Props
| Name | Type | Description |
|---|---|---|
input | `string \ | URL \ |
init.raw | Request | Pass request object directly |
init.customFetch | (req: Request) => Promise<Response> | Override the underlying fetch implementation |
init.strictConnectionProcessing | boolean | Enable RFC 9110 hop-by-hop header processing (trusted environments only) |
init.headers | object | Override or exclude specific headers; set a header to undefined to omit it |
Notes
Accept-Encodingis replaced with an encoding the current runtime can handle- Unnecessary response headers are removed automatically
- The
Connectionheader is ignored by default to prevent Hop-by-Hop Header Injection attacks - Use
strictConnectionProcessing: trueonly in trusted environments
Related
- Adapter Helper
helpers
| Name | Description | Path |
|---|---|---|
| Accepts Helper | HTTP content negotiation — matches client Accept-* headers against server-supported values. | accepts.md |
| Adapter Helper | Runtime-agnostic utilities for accessing environment variables and identifying the current runtime. | adapter.md |
| ConnInfo Helper | Retrieve TCP/UDP connection metadata (remote address, transport, port) from the current request. | conninfo.md |
| Cookie Helper | Read, write, and delete HTTP cookies with optional cookie prefixes. | cookie.md |
| CSS Helper | Write CSS-in-JS using tagged template literals inside JSX. Requires <Style /> in the document head. | css.md |
| Dev Helper | Utilities for inspecting registered routes and the active router during development. | dev.md |
| Factory Helper | Create typed middleware and handlers outside of route definitions, sharing a common Env type. | factory.md |
| HTML Helper | Tagged template literal for writing HTML safely in JavaScript, plus raw() for unescaped output. | html.md |
| JWT Helper | Sign, verify, and decode JSON Web Tokens (HS256 by default). | jwt.md |
| Proxy Helper | Forward incoming requests to an upstream origin with automatic header sanitization. | proxy.md |
| Route Helper | Inspect matched routes, path patterns, and base paths at runtime — useful for nested sub-applications. | route.md |
| SSG Helper | Generate a static site from a Hono application by crawling registered routes and writing HTML files. | ssg.md |
| Streaming Helper | Helpers for HTTP streaming responses: raw binary, plain text, and Server-Sent Events. | streaming.md |
| Testing Helper | Create a type-safe RPC client from a Hono app for use in tests — no running server required. | testing.md |
| WebSocket Helper | Upgrade HTTP connections to WebSocket with a unified event-handler API across runtimes. | websocket.md |
Route Helper
Inspect matched routes, path patterns, and base paths at runtime — useful for nested sub-applications.
Signature / Usage
import { matchedRoutes, routePath, baseRoutePath, basePath } from 'hono/route'
app.get('/api/posts/:id', (c) => {
matchedRoutes(c) // all matched routes (including middleware)
routePath(c) // '/api/posts/:id'
routePath(c, 0) // first matched pattern
routePath(c, -1) // last matched pattern
baseRoutePath(c) // base pattern from app.route()
basePath(c) // '/api' (actual values substituted)
})Options / Props
matchedRoutes(c)
Returns: Array<{ method: string; path: string; handler: Function }> — all routes (and middleware) that matched the current request.
routePath(c, index?)
| Parameter | Type | Description |
|---|---|---|
c | Context | Hono context |
index | number (optional) | Position in matched list; supports negative indexing (Array.at() semantics) |
Returns: string — route pattern (e.g., '/posts/:id').
baseRoutePath(c, index?)
Same signature as routePath. Returns the base path pattern as specified in app.route().
basePath(c)
Returns: string — base path with actual parameter values substituted (e.g., '/api').
Notes
- All helpers support sub-applications composed with
app.route() indexusesArray.at()semantics, so-1refers to the last matched route
Related
- Dev Helper
- Factory Helper
SSG Helper
Generate a static site from a Hono application by crawling registered routes and writing HTML files.
Signature / Usage
import { toSSG } from 'hono/ssg'
import fs from 'node:fs/promises'
const result = await toSSG(app, fs, { dir: './dist' })
// result: { success: boolean; files: string[]; error?: Error }Platform-specific exports also available:
import { toSSG } from 'hono/deno' // Deno
import { toSSG } from 'hono/bun' // BunOptions / Props
toSSG(app, fsModule, options?)
| Parameter | Type | Description |
|---|---|---|
app | Hono | Application with registered routes |
fsModule | FileSystemModule | Object implementing writeFile() and mkdir() |
options.dir | string | Output directory (default: './static') |
options.concurrency | number | Parallel file generation count (default: 2) |
options.extensionMap | Record<string, string> | Maps Content-Type to file extension |
options.plugins | SSGPlugin[] | Plugins for custom generation behavior |
FileSystemModule interface
Must implement:
writeFile(path, data): Promise<void>mkdir(path, { recursive }): Promise<void | string>
Return value
{ success: boolean; files: string[]; error?: Error }
Notes
- Route-to-path mapping:
/→index.html,/path→path.html,/path/→path/index.html - File extension is derived from the route's
Content-Typeresponse header - By default,
defaultPluginskips routes that return non-200 status codes - Custom plugins must explicitly include
defaultPluginto retain the default skip behavior
Related
- Dev Helper
Streaming Helper
Helpers for HTTP streaming responses: raw binary, plain text, and Server-Sent Events.
Signature / Usage
import { stream, streamText, streamSSE } from 'hono/streaming'
// Raw streaming response
app.get('/stream', (c) =>
stream(c, async (s) => {
await s.write(new Uint8Array([1, 2, 3]))
await s.writeln('hello')
await s.pipe(readableStream)
})
)
// Plain-text streaming (sets Content-Type: text/plain, Transfer-Encoding: chunked)
app.get('/text', (c) =>
streamText(c, async (s) => {
await s.writeln('Hello!')
await s.sleep(100)
await s.writeln('World!')
})
)
// Server-Sent Events
app.get('/sse', (c) =>
streamSSE(c, async (s) => {
await s.writeSSE({ data: 'event data', event: 'update', id: '1' })
})
)Options / Props
| Name | Type | Description |
|---|---|---|
c | Context | Hono context |
callback | (stream) => Promise<void> | Async function performing write operations |
errorHandler | (err, stream) => Promise<void> | Optional; called on errors during streaming |
Stream object methods
| Method | Description |
|---|---|
write(data: Uint8Array) | Write binary data |
writeln(text: string) | Write text with trailing newline |
writeSSE({ data, event?, id? }) | Write an SSE frame |
sleep(ms: number) | Pause execution |
pipe(ReadableStream) | Pipe a readable stream |
onAbort(cb) | Register callback for stream abort |
Notes
- Errors thrown inside the callback do not trigger Hono's
onErrorhook because the response has already started - On Cloudflare Workers / Wrangler, set the
Content-Encodingheader to'Identity'for correct streaming behavior - The stream is automatically closed after the callback completes
Related
- WebSocket Helper
Testing Helper
Create a type-safe RPC client from a Hono app for use in tests — no running server required.
Signature / Usage
import { testClient } from 'hono/testing'
const app = new Hono().get('/search', (c) => {
return c.json({ results: [] })
})
const client = testClient(app)
const res = await client.search.$get({ query: { q: 'hono' } })
const data = await res.json()With auth headers:
const res = await client.search.$get(
{ query: { q: 'hono' } },
{ headers: { Authorization: `Bearer ${token}` } }
)Options / Props
testClient(app)
| Parameter | Type | Description |
|---|---|---|
app | Hono | Application instance with routes defined via chained methods |
Returns: typed client object matching the app's route definitions.
Client method call signature
client[routePath].$[method](params?, requestInit?)| Parameter | Type | Description |
|---|---|---|
params | object | Route params, query, form, json body depending on route definition |
requestInit | RequestInit & { headers?: ... } | Optional fetch init (headers, etc.) |
Returns: Promise<Response>
Notes
- Routes must be defined using chained methods directly on the
Honoinstance for type inference to work:new Hono().get(...), notapp.get(...)after separate instantiation - Response data is accessed via
await res.json()orres.status - No HTTP server is started; requests are handled in-process
Related
- Factory Helper
WebSocket Helper
Upgrade HTTP connections to WebSocket with a unified event-handler API across runtimes.
Signature / Usage
import { upgradeWebSocket } from 'hono/cloudflare-workers' // or deno / bun adapter
// For Node.js (v1.14.0+): import { createNodeWebSocket } from '@hono/node-server'
app.get(
'/ws',
upgradeWebSocket((c) => ({
onOpen(event, ws) {
ws.send('connected')
},
onMessage(event, ws) {
ws.send(`Echo: ${event.data}`)
},
onClose() {},
onError(event) {},
}))
)Options / Props
| Handler | Description |
|---|---|
onOpen(event, ws) | Connection established (not supported on Cloudflare Workers) |
onMessage(event, ws) | Message received from client; event.data contains the payload |
onClose(event, ws) | Connection closed |
onError(event, ws) | Connection error |
ws object methods
| Method | Description |
|---|---|
ws.send(data) | Send data to the client |
ws.close() | Close the connection |
Notes
- For Node.js, WebSocket support is built into
@hono/node-server(v1.14.0+).@hono/node-wsis deprecated; usecreateNodeWebSocketfrom@hono/node-serverdirectly - Middleware that mutates response headers (e.g., CORS) can conflict with
upgradeWebSocketand cause header immutability errors - Supports RPC mode for type-safe client connections via
hono/client
Related
- Factory Helper
Basic Auth Middleware
Adds HTTP Basic Authentication to routes. Responds with 401 when credentials are invalid.
Signature / Usage
import { basicAuth } from 'hono/basic-auth'
app.use('/auth/*', basicAuth({ username: 'hono', password: 'acoolproject' }))Options / Props
| Name | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Authenticating user's username |
password | string | Yes | Password for the provided username |
realm | string | No | Domain name for WWW-Authenticate header (default: "Secure Area") |
hashFunction | Function | No | Custom password hashing function for secure comparison |
verifyUser | `(username, password, c) => boolean \ | Promise<boolean>` | No |
invalidUserMessage | `string \ | object \ | MessageFunction` |
onAuthSuccess | `(c, username) => void \ | Promise<void>` | No |
Notes
- Multiple users: pass additional
{ username, password }objects as extra arguments after the first config object. - When using
verifyUser, theusernameandpasswordfields are not required.
Related
- Bearer Auth
- JWT
Bearer Auth Middleware
Validates Bearer tokens in the Authorization header. Responds with 401 for missing/invalid tokens and 400 for malformed headers.
Signature / Usage
import { bearerAuth } from 'hono/bearer-auth'
app.use('/api/*', bearerAuth({ token: 'honoiscool' }))Options / Props
| Name | Type | Required | Description |
|---|---|---|---|
token | `string \ | string[]` | Yes* |
verifyToken | `(token, c) => boolean \ | Promise<boolean>` | Yes* |
realm | string | No | Domain name in WWW-Authenticate challenge (default: "") |
prefix | string | No | Authorization header schema identifier (default: "Bearer") |
headerName | string | No | Custom header name to check (default: "Authorization") |
hashFunction | Function | No | Hashing function for secure token comparison |
noAuthenticationHeader | object | No | Custom error response when header is missing |
invalidAuthenticationHeader | object | No | Custom error response for malformed headers |
invalidToken | object | No | Custom error response for failed token validation |
*Either token or verifyToken is required.
Notes
- Token must match the regex
/[A-Za-z0-9._~+/-]+=*/; tokens failing this pattern trigger a400error. - Base64-encoded JWTs are supported but JWT format is not required.
Related
- Basic Auth
- JWT
Body Limit Middleware
Enforces a maximum request body size. Returns a configurable error response when the limit is exceeded.
Signature / Usage
import { bodyLimit } from 'hono/body-limit'
app.post(
'/upload',
bodyLimit({
maxSize: 50 * 1024, // 50kb
onError: (c) => c.text('overflow :(', 413),
}),
async (c) => {
const body = await c.req.parseBody()
return c.text('pass :)')
}
)Options / Props
| Name | Type | Required | Description |
|---|---|---|---|
maxSize | number | Yes | Maximum body size in bytes (default: 100 * 1024 = 100 KB) |
onError | `(c: Context) => Response \ | Promise<Response>` | No |
Notes
- The middleware checks
Content-Lengthheader first; only if absent does it stream and measure body size. - On Bun with bodies exceeding 128 MiB, configure
Bun.serve({ maxRequestBodySize })or theonErrorhandler will not be triggered.
Cache Middleware
Caches responses using the Cache API (available on Cloudflare Workers, Deno, etc.).
Signature / Usage
import { cache } from 'hono/cache'
app.get('*', cache({
cacheName: 'my-app',
cacheControl: 'max-age=3600',
}))Options / Props
| Name | Type | Required | Description |
|---|---|---|---|
cacheName | `string \ | (c) => string \ | Promise<string>` |
wait | boolean | No | Wait for cache write to complete before proceeding (default: false; required for Deno) |
cacheControl | string | No | Cache-Control header directives to set on responses |
vary | `string \ | string[]` | No |
keyGenerator | `(c) => string \ | Promise<string>` | No |
cacheableStatusCodes | number[] | No | HTTP status codes to cache (default: [200]) |
onCacheNotAvailable | `(() => void) \ | false` | No |
Notes
- On Cloudflare Workers,
Cache-Controlheaders from the origin response are respected automatically. - On Deno, set
wait: truebecause cache invalidation is not automatic. - This middleware requires a runtime that implements the Cache API.
Related
- ETag
Combine Middleware
Utility functions for composing multiple middleware with logical conditions: some (OR), every (AND), except (NOT).
Signature / Usage
import { some, every, except } from 'hono/combine'
// Run the first middleware that succeeds (OR)
app.use('/api/*', some(bearerAuth({ token }), myRateLimit({ limit: 100 })))
// Run all middleware; stop if any fails (AND)
app.use('/api/*', every(bearerAuth({ token }), myRateLimit({ limit: 100 })))
// Skip middleware for matching paths
app.use('/api/*', except('/api/public/*', bearerAuth({ token })))Functions
| Function | Behavior |
|---|---|
some(...middleware) | Runs middleware in order; stops after the first one that does not throw |
every(...middleware) | Runs all middleware; stops and throws if any one fails |
except(condition, ...middleware) | Applies middleware to all requests except those matching the condition |
Options / Props for except
| Name | Type | Description |
|---|---|---|
condition | `string \ | string[] \ |
Notes
someandeverycan be nested together for complex conditional access control.exceptpath patterns support wildcards (e.g.'/api/public/*').
Related
- Basic Auth
- Bearer Auth
- IP Restriction
Compress Middleware
Compresses response bodies using gzip or deflate based on the client's Accept-Encoding header.
Signature / Usage
import { compress } from 'hono/compress'
app.use(compress())Options / Props
| Name | Type | Default | Description |
|---|---|---|---|
encoding | `'gzip' \ | 'deflate'` | Auto |
threshold | number | 1024 | Minimum response size in bytes before compression is applied |
contentTypeFilter | `RegExp \ | (contentType: string) => boolean` | — |
Notes
- On Cloudflare Workers and Deno Deploy, responses are compressed automatically; this middleware is not needed on those platforms.
COMPRESSIBLE_CONTENT_TYPE_REGEXcan be imported fromhono/compressand extended to customize the default compressible content types.
Context Storage Middleware
Stores the Hono Context in AsyncLocalStorage, enabling access to it outside of request handlers (e.g. in service functions).
Signature / Usage
import { contextStorage, getContext } from 'hono/context-storage'
type Env = { Variables: { message: string }; Bindings: { KV: KVNamespace } }
const app = new Hono<Env>()
app.use(contextStorage())
app.use(async (c, next) => { c.set('message', 'Hello'); await next() })
// Accessible anywhere in the call stack
const getMessage = () => getContext<Env>().var.messageFunctions
| Function | Signature | Description |
|---|---|---|
contextStorage() | contextStorage() | Middleware that enables global context access; no parameters |
getContext<Env>() | () => Context<Env> | Returns the current Context; throws if unavailable |
tryGetContext<Env>() | `() => Context<Env> \ | undefined` |
Notes
- Requires
AsyncLocalStoragesupport from the runtime. - On Cloudflare Workers, add
nodejs_compatornodejs_alscompatibility flag towrangler.toml. - Use
tryGetContextwhen the function may be called outside of a request context.
CORS Middleware
Handles Cross-Origin Resource Sharing (CORS) headers. Must be applied before route definitions.
Signature / Usage
import { cors } from 'hono/cors'
app.use('/api/*', cors())
// With configuration
app.use('/api/*', cors({
origin: 'https://example.com',
allowMethods: ['GET', 'POST'],
credentials: true,
}))Options / Props
| Name | Type | Default | Description |
|---|---|---|---|
origin | `string \ | string[] \ | (origin, c) => string` |
allowMethods | `string[] \ | ((origin: string, c: Context) => string[])` | ['GET','HEAD','PUT','POST','DELETE','PATCH'] |
allowHeaders | string[] | [] | Access-Control-Allow-Headers value |
maxAge | number | — | Access-Control-Max-Age value in seconds |
credentials | boolean | — | Access-Control-Allow-Credentials value |
exposeHeaders | string[] | [] | Access-Control-Expose-Headers value |
Notes
- CORS middleware must be called before route definitions.
- When using Hono with Vite, set
server.cors: falseinvite.config.tsto prevent conflicts. - Use a callback for
originto allow multiple origins dynamically (e.g. read fromc.env).
Related
- Secure Headers
CSRF Protection Middleware
Protects against Cross-Site Request Forgery by validating the Origin or Sec-Fetch-Site header on unsafe HTTP methods (POST, PUT, DELETE, PATCH).
Signature / Usage
import { csrf } from 'hono/csrf'
app.use(csrf())
// With specific origin
app.use(csrf({ origin: 'https://example.com' }))Options / Props
| Name | Type | Description |
|---|---|---|
origin | `string \ | string[] \ |
secFetchSite | `string \ | string[] \ |
Notes
- Only validates unsafe methods with form-compatible content types; GET/HEAD are unaffected.
- A request passes if either the
Origincheck or theSec-Fetch-Sitecheck succeeds. - Old browsers that do not send
Originheaders, or reverse proxies that strip them, may bypass this middleware. In such cases use CSRF token methods instead. - For dynamic origin functions, always verify the protocol; never use forward (prefix) matching.
ETag Middleware
Generates and validates ETag headers to enable HTTP caching via conditional requests (If-None-Match).
Signature / Usage
import { etag } from 'hono/etag'
app.use('/etag/*', etag())
app.get('/etag/abc', (c) => c.text('Hono is cool'))Options / Props
| Name | Type | Default | Description |
|---|---|---|---|
weak | boolean | false | Enables weak validation by prefixing the ETag value with W/ |
retainedHeaders | string[] | RETAINED_304_HEADERS | Headers to retain in 304 Not Modified responses |
generateDigest | `(body: Uint8Array) => ArrayBuffer \ | Promise<ArrayBuffer>` | SHA-1 |
Notes
RETAINED_304_HEADERS(exported fromhono/etag) contains the RFC-required headers:Cache-Control,Content-Location,Date,ETag,Expires,Vary.- Extend retained headers by spreading
RETAINED_304_HEADERSwith your custom headers.
Related
- Cache
IP Restriction Middleware
Restricts access based on client IP addresses using allowlists and denylists. Supports CIDR notation.
Signature / Usage
import { ipRestriction } from 'hono/ip-restriction'
import { getConnInfo } from 'hono/bun' // use the helper for your runtime
app.use('*', ipRestriction(getConnInfo, {
denyList: ['192.168.2.0/24'],
allowList: ['127.0.0.1', '::1'],
}))Options / Props
| Name | Type | Description |
|---|---|---|
getConnInfo | (c: Context) => ConnInfo | Required. Runtime-specific connection info helper |
options.denyList | string[] | IP addresses or ranges to block |
options.allowList | string[] | IP addresses or ranges to allow |
errorHandler | `(remote, c) => Response \ | Promise<Response>` |
Supported IP Formats
| Format | Example |
|---|---|
| IPv4 static | 192.168.2.0 |
| IPv4 CIDR | 192.168.2.0/24 |
| IPv6 static | ::1 |
| IPv6 CIDR | ::1/10 |
| Wildcard | * |
Notes
- Use the
getConnInfohelper matching your runtime:hono/bun,hono/deno,hono/node-server, etc. allowListtakes precedence overdenyListwhen an IP matches both.- IPv6 addresses are compared in canonical compressed form. Addresses passed in expanded form (e.g.
0000:0000::1) are automatically normalized. This canonicalization was added in v4.12.21 to prevent bypass via non-canonical representations.
Related
- Combine
JSX Renderer Middleware
Provides a layout system for JSX-based HTML rendering. Wraps route responses in a shared layout component.
Signature / Usage
import { jsxRenderer, useRequestContext } from 'hono/jsx-renderer'
app.use(jsxRenderer(({ children }) => (
<html>
<body>{children}</body>
</html>
)))
app.get('/', (c) => c.render(<h1>Hello</h1>))Options / Props
| Name | Type | Default | Description |
|---|---|---|---|
docType | `boolean \ | string` | true |
stream | `boolean \ | Record<string, string>` | — |
Options can also be a function (c: Context) => RendererOptions for dynamic configuration per request.
Notes
useRequestContext()returns the currentContextinside JSX components.- Nested layouts are supported via the
Layoutcomponent prop. - Streaming integrates with
<Suspense>for async components. useRequestContext()is incompatible with Deno'sprecompileJSX option; use"jsx": "react-jsx"intsconfig.jsoninstead.- Declare a custom
ContextRendererinterface to pass additional props to layouts.
JWK Middleware
Validates JWTs using JSON Web Keys (JWK). Fetches public keys from a JWKS endpoint or accepts them directly.
Signature / Usage
import { jwk } from 'hono/jwk'
app.use('/auth/*', jwk({
jwks_uri: 'https://backend/.well-known/jwks.json',
alg: ['RS256'],
}))
// Retrieve payload in handler
app.get('/auth/page', (c) => {
const payload = c.get('jwtPayload')
return c.json(payload)
})Options / Props
| Name | Type | Required | Description |
|---|---|---|---|
alg | AsymmetricAlgorithm[] | Yes | Allowed asymmetric algorithms (RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA) |
keys | `HonoJsonWebKey[] \ | (c) => Promise<HonoJsonWebKey[]>` | No* |
jwks_uri | `string \ | (c) => Promise<string>` | No* |
allow_anon | boolean | No | Permit unauthenticated requests (default: false) |
cookie | string | No | Cookie key to extract token from instead of header |
headerName | string | No | Header name for JWT (default: Authorization) |
verification | VerifyOptions | No | Claim validation config (iss, aud, exp, nbf, iat) |
*Either keys or jwks_uri is required.
Notes
- Symmetric algorithms are rejected; only asymmetric algorithms are allowed.
- Requires a
kidheader in the JWT matching one of the provided keys. - Time-based claims (
nbf,exp,iat) are validated by default. - The
Authorizationheader must include a scheme, e.g.Bearer <token>. - Pass a second
RequestInitargument tojwk()to configure the JWKS fetch request (e.g. custom headers).
Related
- JWT
- Bearer Auth
JWT Middleware
Validates JSON Web Tokens from the Authorization header. Stores the decoded payload in context via c.get('jwtPayload').
Signature / Usage
import { jwt } from 'hono/jwt'
import type { JwtVariables } from 'hono/jwt'
type Env = { Variables: JwtVariables }
const app = new Hono<Env>()
app.use('/api/*', jwt({ secret: 'it-is-very-secret' }))
app.get('/api/page', (c) => {
const payload = c.get('jwtPayload')
return c.json(payload)
})Options / Props
| Name | Type | Required | Description |
|---|---|---|---|
secret | string | Yes | Secret key for token verification |
alg | string | No | Signing algorithm (default: HS256). Supported: HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA |
cookie | string | No | Cookie key to retrieve the JWT from instead of the header |
headerName | string | No | Custom header name (default: Authorization) |
verifyOptions.iss | `string \ | RegExp` | No |
verifyOptions.nbf | boolean | No | Verify "not before" claim (default: true) |
verifyOptions.iat | boolean | No | Verify "issued at" claim (default: true) |
verifyOptions.exp | boolean | No | Verify expiration time (default: true) |
Notes
- The
Authorizationheader must include theBearerscheme exactly (e.g.Bearer <token>). Other schemes are rejected with a401. This validation was tightened in v4.12.21. - For dynamic secrets (e.g. from
c.env), wrap the middleware in a custom handler function.
Related
- Bearer Auth
- JWK
Language Middleware
Detects the user's preferred language from query parameters, cookies, or the Accept-Language header. Stores the result in c.get('language').
Signature / Usage
import { languageDetector } from 'hono/language'
app.use(languageDetector({
supportedLanguages: ['en', 'ja', 'fr'],
fallbackLanguage: 'en',
}))
app.get('/', (c) => c.text(`Language: ${c.get('language')}`))Options / Props
| Name | Type | Default | Description |
|---|---|---|---|
supportedLanguages | string[] | — | Required. Permitted language codes; must include fallbackLanguage |
fallbackLanguage | string | — | Required. Default language when detection fails |
order | DetectorType[] | ['querystring','cookie','header'] | Detection order: querystring, cookie, header, path |
lookupQueryString | string | 'lang' | Query parameter name |
lookupCookie | string | 'language' | Cookie name |
lookupFromHeaderKey | string | 'accept-language' | HTTP header name |
lookupFromPathIndex | number | 0 | URL path segment index for language code |
caches | `CacheType[] \ | false` | ['cookie'] |
cookieOptions | object | — | Cookie options: domain, sameSite, secure, maxAge, httpOnly, path |
ignoreCase | boolean | true | Case-insensitive language matching |
convertDetectedLanguage | (lang: string) => string | — | Transform detected language codes before matching |
debug | boolean | false | Log detection steps to console |
Notes
fallbackLanguagemust exist insupportedLanguagesor initialization fails.- Progressive locale matching is applied:
ja-JPmatchesjawhen an exact match is unavailable. - Failed detections silently use
fallbackLanguage.
Logger Middleware
Logs incoming requests and outgoing responses to the console, including method, path, status code, and response time.
Signature / Usage
import { logger } from 'hono/logger'
app.use(logger())Options / Props
| Name | Type | Description |
|---|---|---|
fn | (str: string, ...rest: string[]) => void | Custom print function (default: console.log) |
Notes
- Status codes are color-coded by default in environments that support ANSI colors.
- Set the
NO_COLORenvironment variable to disable color output. - On Cloudflare Workers (no
process.env), output defaults to plaintext. - Response time is logged in a human-readable format (ms or s).
Method Override Middleware
Allows overriding the HTTP method of a request via a form field, header, or query parameter. Useful for HTML forms that only support GET and POST.
Signature / Usage
import { methodOverride } from 'hono/method-override'
const app = new Hono()
app.use('/posts', methodOverride({ app }))
app.delete('/posts', (c) => c.text('Deleted'))<form method="POST" action="/posts">
<input type="hidden" name="_method" value="DELETE" />
<button type="submit">Delete</button>
</form>Options / Props
| Name | Type | Required | Description |
|---|---|---|---|
app | Hono | Yes | The Hono application instance |
form | string | No | Form field name containing the override value (default: _method) |
header | string | No | Header name containing the override value |
query | string | No | Query parameter name containing the override value |
Notes
- Only one source (
form,header, orquery) should be configured at a time.
Pretty JSON Middleware
Enables formatted (indented) JSON responses. Activated by the ?pretty query parameter or forced globally.
Signature / Usage
import { prettyJSON } from 'hono/pretty-json'
app.use(prettyJSON())
app.get('/', (c) => c.json({ message: 'Hono!' }))
// GET /?pretty → formatted JSONOptions / Props
| Name | Type | Default | Description |
|---|---|---|---|
space | number | 2 | Number of spaces for indentation |
query | string | "pretty" | Query string parameter name that triggers pretty printing |
force | boolean | false | Always prettify JSON regardless of query parameter |
Middleware
| Name | Description | Path |
|---|---|---|
| Basic Auth Middleware | Adds HTTP Basic Authentication to routes. | basic-auth.md |
| Bearer Auth Middleware | Validates Bearer tokens in the Authorization header. | bearer-auth.md |
| Body Limit Middleware | Enforces a maximum request body size. | body-limit.md |
| Cache Middleware | Caches responses using the Cache API. | cache.md |
| Combine Middleware | Utility functions for composing multiple middleware. | combine.md |
| Compress Middleware | Compresses response bodies using gzip or deflate. | compress.md |
| Context Storage Middleware | Stores the Hono Context in AsyncLocalStorage. | context-storage.md |
| CORS Middleware | Handles Cross-Origin Resource Sharing (CORS) headers. | cors.md |
| CSRF Protection Middleware | Protects against Cross-Site Request Forgery. | csrf.md |
| ETag Middleware | Generates and validates ETag headers. | etag.md |
| IP Restriction Middleware | Restricts access based on client IP addresses. | ip-restriction.md |
| JSX Renderer Middleware | Provides a layout system for JSX-based HTML rendering. | jsx-renderer.md |
| JWT Middleware | Validates JSON Web Tokens from the Authorization header. | jwt.md |
| JWK Middleware | Validates JWTs using JSON Web Keys (JWK). | jwk.md |
| Language Middleware | Detects the user's preferred language. | language.md |
| Logger Middleware | Logs incoming requests and outgoing responses. | logger.md |
| Method Override Middleware | Allows overriding the HTTP method of a request. | method-override.md |
| Pretty JSON Middleware | Enables formatted (indented) JSON responses. | pretty-json.md |
| Request ID Middleware | Assigns a unique ID to each request. | request-id.md |
| Secure Headers Middleware | Sets security-related HTTP response headers. | secure-headers.md |
| Timeout Middleware | Rejects requests that exceed a specified duration. | timeout.md |
| Timing Middleware (Server-Timing) | Adds Server-Timing headers to responses. | timing.md |
| Trailing Slash Middleware | Redirects requests to normalize trailing slashes. | trailing-slash.md |
Request ID Middleware
Assigns a unique ID to each request, readable via c.get('requestId'). Reads from an incoming header if present.
Signature / Usage
import { requestId } from 'hono/request-id'
app.use('*', requestId())
app.get('/', (c) => c.text(`Your request id is ${c.get('requestId')}`))Options / Props
| Name | Type | Default | Description |
|---|---|---|---|
limitLength | number | 255 | Maximum length of the request ID |
headerName | string | X-Request-Id | Header name for receiving/setting the request ID |
generator | (c) => string | crypto.randomUUID() | Custom function to generate request IDs |
Notes
- Import
RequestIdVariablesfor type-safec.get('requestId'):new Hono<{ Variables: RequestIdVariables }>(). - Set
headerNameto an empty string to disable reading the ID from incoming headers. - Node.js 20+ is required for
crypto.randomUUID(); the Node.js adapter handles this automatically. - Platform-specific IDs (AWS Lambda, Cloudflare, Deno, Fastly) can be integrated via the
generatoroption.
Secure Headers Middleware
Sets security-related HTTP response headers with sensible defaults. Modeled after Helmet.js.
Signature / Usage
import { secureHeaders } from 'hono/secure-headers'
app.use(secureHeaders())Options / Props
Each option accepts true (enable default), false (suppress header), or a custom string value.
| Name | Default Value | Description |
|---|---|---|
xFrameOptions | "SAMEORIGIN" | X-Frame-Options header |
xXssProtection | "0" | X-XSS-Protection header |
strictTransportSecurity | "max-age=15552000; includeSubDomains" | Strict-Transport-Security header |
xContentTypeOptions | "nosniff" | X-Content-Type-Options header |
xDnsPrefetchControl | "off" | X-DNS-Prefetch-Control header |
xDownloadOptions | "noopen" | X-Download-Options header |
xPermittedCrossDomainPolicies | "none" | X-Permitted-Cross-Domain-Policies header |
crossOriginResourcePolicy | "same-origin" | Cross-Origin-Resource-Policy header |
crossOriginOpenerPolicy | "same-origin" | Cross-Origin-Opener-Policy header |
crossOriginEmbedderPolicy | false | Cross-Origin-Embedder-Policy header (disabled by default) |
originAgentCluster | "?1" | Origin-Agent-Cluster header |
referrerPolicy | "no-referrer" | Referrer-Policy header |
contentSecurityPolicy | — | Object with CSP directives (defaultSrc, scriptSrc, styleSrc, etc.) |
contentSecurityPolicyReportOnly | — | Report-only variant of CSP |
trustedTypes | — | Trusted-Types policy name configuration |
requireTrustedTypesFor | — | Require-Trusted-Types-For enforcement scope |
reportingEndpoints | — | Reporting-Endpoints configuration |
permissionsPolicy | — | Object mapping feature names to arrays of origins or booleans |
Notes
- The
NONCEconstant (imported fromhono/secure-headers) generates a unique per-request value for CSP nonces. - Import
SecureHeadersVariablesfor type-safe nonce access viac.get('secureHeadersNonce'). - Be cautious about middleware ordering: later middleware overrides earlier header values for the same header name.
Related
- CORS
- CSRF
Timeout Middleware
Rejects requests that exceed a specified duration. Throws an HTTPException (default 504) on timeout.
Signature / Usage
import { timeout } from 'hono/timeout'
import { HTTPException } from 'hono/http-exception'
app.use('/api', timeout(5000))
// With custom error
app.use('/api/long-process', timeout(60000, (c) =>
new HTTPException(408, { message: 'Request timeout. Please try again later.' })
))Options / Props
| Name | Type | Required | Description |
|---|---|---|---|
duration | number | Yes | Timeout duration in milliseconds |
customException | (c) => HTTPException | No | Factory function returning a custom HTTPException on timeout |
Notes
- The
customExceptioncan be a factory function or a staticHTTPExceptioninstance. - Timeout middleware cannot be used with streaming responses. For streams, use
setTimeoutandstream.close()manually. - Be cautious of middleware ordering when combining with error-handling middleware.
Timing Middleware (Server-Timing)
Adds Server-Timing headers to responses for performance measurement and profiling.
Signature / Usage
import { timing, startTime, endTime, setMetric, wrapTime } from 'hono/timing'
import type { TimingVariables } from 'hono/timing'
type Env = { Variables: TimingVariables }
const app = new Hono<Env>()
app.use(timing())
app.get('/', async (c) => {
startTime(c, 'db')
const data = await db.find()
endTime(c, 'db')
return c.json(data)
})Functions
| Function | Signature | Description |
|---|---|---|
timing() | timing(options?) | Middleware initializer; adds performance metrics to response headers |
setMetric | (c, name, duration?, description?) | Register a custom metric with optional duration (ms) and label |
startTime | (c, name) | Begin timing a labeled operation |
endTime | (c, name) | Conclude timing a previously started operation |
wrapTime | (c, name, promise) | Wrap a Promise to automatically measure its execution time |
Options / Props
| Name | Type | Default | Description |
|---|---|---|---|
total | boolean | true | Display cumulative response time |
enabled | `boolean \ | (c) => boolean` | true |
totalDescription | string | "Total Response Time" | Label for total duration metric |
autoEnd | boolean | — | Auto-conclude timers at request completion |
crossOrigin | `boolean \ | string \ | (c) => boolean \ |
Notes
- On Cloudflare Workers, timer metrics may be inaccurate because timers reflect only the time of the last I/O operation.
- Declare
Variables: TimingVariableson your Hono app type for proper context inference.
Trailing Slash Middleware
Redirects requests to normalize trailing slashes. Two functions are provided: appendTrailingSlash and trimTrailingSlash.
Signature / Usage
import { appendTrailingSlash, trimTrailingSlash } from 'hono/trailing-slash'
// Redirect /about/me → /about/me/
const app = new Hono({ strict: true })
app.use(appendTrailingSlash())
app.get('/about/me/', (c) => c.text('With trailing slash'))
// Redirect /about/me/ → /about/me
const app2 = new Hono({ strict: true })
app2.use(trimTrailingSlash())
app2.get('/about/me', (c) => c.text('Without trailing slash'))Options / Props
Both functions accept the same options object.
| Name | Type | Default | Description |
|---|---|---|---|
alwaysRedirect | boolean | false | Redirect before route execution instead of waiting for a 404 response. Required for wildcard routes. |
Notes
- By default, redirection only occurs when the response status is
404. alwaysRedirect: trueis needed for wildcard routes (/my-path/*) because those routes always match and never produce a404.- Only applies to
GETrequests.