This is the full developer documentation for Hono.
# Start of Hono documentation
# Hono
Hono - _**means flame🔥 in Japanese**_ - is a small, simple, and ultrafast web framework built on Web Standards.
It works on any JavaScript runtime: Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Netlify, AWS Lambda, Lambda@Edge, and Node.js.
Fast, but not only fast.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hono!'))
export default app
```
## Quick Start
Just run this:
::: code-group
```sh [npm]
npm create hono@latest
```
```sh [yarn]
yarn create hono
```
```sh [pnpm]
pnpm create hono@latest
```
```sh [bun]
bun create hono@latest
```
```sh [deno]
deno init --npm hono@latest
```
:::
## Features
- **Ultrafast** 🚀 - The router `RegExpRouter` is really fast. Not using linear loops. Fast.
- **Lightweight** 🪶 - The `hono/tiny` preset is under 14kB. Hono has zero dependencies and uses only the Web Standards.
- **Multi-runtime** 🌍 - Works on Cloudflare Workers, Fastly Compute, Deno, Bun, AWS Lambda, or Node.js. The same code runs on all platforms.
- **Batteries Included** 🔋 - Hono has built-in middleware, custom middleware, third-party middleware, and helpers. Batteries included.
- **Delightful DX** 😃 - Super clean APIs. First-class TypeScript support. Now, we've got "Types".
## Use-cases
Hono is a simple web application framework similar to Express, without a frontend.
But it runs on CDN Edges and allows you to construct larger applications when combined with middleware.
Here are some examples of use-cases.
- Building Web APIs
- Proxy of backend servers
- Front of CDN
- Edge application
- Base server for a library
- Full-stack application
## Who is using Hono?
| Project | Platform | What for? |
| ---------------------------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------- |
| [cdnjs](https://cdnjs.com) | Cloudflare Workers | A free and open-source CDN service. _Hono is used for the API server_. |
| [Cloudflare D1](https://www.cloudflare.com/developer-platform/d1/) | Cloudflare Workers | Serverless SQL databases. _Hono is used for the internal API server_. |
| [Cloudflare Workers KV](https://www.cloudflare.com/developer-platform/workers-kv/) | Cloudflare Workers | Serverless key-value database. _Hono is used for the internal API server_. |
| [BaseAI](https://baseai.dev) | Local AI Server | Serverless AI agent pipes with memory. An open-source agentic AI framework for web. _API server with Hono_. |
| [Unkey](https://unkey.dev) | Cloudflare Workers | An open-source API authentication and authorization. _Hono is used for the API server_. |
| [OpenStatus](https://openstatus.dev) | Bun | An open-source website & API monitoring platform. _Hono is used for the API server_. |
| [Deno Benchmarks](https://deno.com/benchmarks) | Deno | A secure TypeScript runtime built on V8. _Hono is used for benchmarking_. |
| [Clerk](https://clerk.com) | Cloudflare Workers | An open-source User Management Platform. _Hono is used for the API server_. |
And the following.
- [Drivly](https://driv.ly/) - Cloudflare Workers
- [repeat.dev](https://repeat.dev/) - Cloudflare Workers
Do you want to see more? See [Who is using Hono in production?](https://github.com/orgs/honojs/discussions/1510).
## Hono in 1 minute
A demonstration to create an application for Cloudflare Workers with Hono.

## Ultrafast
**Hono is the fastest**, compared to other routers for Cloudflare Workers.
```
Hono x 402,820 ops/sec ±4.78% (80 runs sampled)
itty-router x 212,598 ops/sec ±3.11% (87 runs sampled)
sunder x 297,036 ops/sec ±4.76% (77 runs sampled)
worktop x 197,345 ops/sec ±2.40% (88 runs sampled)
Fastest is Hono
✨ Done in 28.06s.
```
See [more benchmarks](/docs/concepts/benchmarks).
## Lightweight
**Hono is so small**. With the `hono/tiny` preset, its size is **under 14KB** when minified. There are many middleware and adapters, but they are bundled only when used. For context, the size of Express is 572KB.
```
$ npx wrangler dev --minify ./src/index.ts
⛅️ wrangler 2.20.0
--------------------
⬣ Listening at http://0.0.0.0:8787
- http://127.0.0.1:8787
- http://192.168.128.165:8787
Total Upload: 11.47 KiB / gzip: 4.34 KiB
```
## Multiple routers
**Hono has multiple routers**.
**RegExpRouter** is the fastest router in the JavaScript world. It matches the route using a single large Regex created before dispatch. With **SmartRouter**, it supports all route patterns.
**LinearRouter** registers the routes very quickly, so it's suitable for an environment that initializes applications every time. **PatternRouter** simply adds and matches the pattern, making it small.
See [more information about routes](/docs/concepts/routers).
## Web Standards
Thanks to the use of the **Web Standards**, Hono works on a lot of platforms.
- Cloudflare Workers
- Cloudflare Pages
- Fastly Compute
- Deno
- Bun
- Vercel
- AWS Lambda
- Lambda@Edge
- Others
And by using [a Node.js adapter](https://github.com/honojs/node-server), Hono works on Node.js.
See [more information about Web Standards](/docs/concepts/web-standard).
## Middleware & Helpers
**Hono has many middleware and helpers**. This makes "Write Less, do more" a reality.
Out of the box, Hono provides middleware and helpers for:
- [Basic Authentication](/docs/middleware/builtin/basic-auth)
- [Bearer Authentication](/docs/middleware/builtin/bearer-auth)
- [Body Limit](/docs/middleware/builtin/body-limit)
- [Cache](/docs/middleware/builtin/cache)
- [Compress](/docs/middleware/builtin/compress)
- [Context Storage](/docs/middleware/builtin/context-storage)
- [Cookie](/docs/helpers/cookie)
- [CORS](/docs/middleware/builtin/cors)
- [ETag](/docs/middleware/builtin/etag)
- [html](/docs/helpers/html)
- [JSX](/docs/guides/jsx)
- [JWT Authentication](/docs/middleware/builtin/jwt)
- [Logger](/docs/middleware/builtin/logger)
- [Language](/docs/middleware/builtin/language)
- [Pretty JSON](/docs/middleware/builtin/pretty-json)
- [Secure Headers](/docs/middleware/builtin/secure-headers)
- [SSG](/docs/helpers/ssg)
- [Streaming](/docs/helpers/streaming)
- [GraphQL Server](https://github.com/honojs/middleware/tree/main/packages/graphql-server)
- [Firebase Authentication](https://github.com/honojs/middleware/tree/main/packages/firebase-auth)
- [Sentry](https://github.com/honojs/middleware/tree/main/packages/sentry)
- Others!
For example, adding ETag and request logging only takes a few lines of code with Hono:
```ts
import { Hono } from 'hono'
import { etag } from 'hono/etag'
import { logger } from 'hono/logger'
const app = new Hono()
app.use(etag(), logger())
```
See [more information about Middleware](/docs/concepts/middleware).
## Developer Experience
Hono provides a delightful "**Developer Experience**".
Easy access to Request/Response thanks to the `Context` object.
Moreover, Hono is written in TypeScript. Hono has "**Types**".
For example, the path parameters will be literal types.

And, the Validator and Hono Client `hc` enable the RPC mode. In RPC mode,
you can use your favorite validator such as Zod and easily share server-side API specs with the client and build type-safe applications.
See [Hono Stacks](/docs/concepts/stacks).
# Third-party Middleware
Third-party middleware refers to middleware not bundled within the Hono package.
Most of this middleware leverages external libraries.
### Authentication
- [Auth.js(Next Auth)](https://github.com/honojs/middleware/tree/main/packages/auth-js)
- [Clerk Auth](https://github.com/honojs/middleware/tree/main/packages/clerk-auth)
- [OAuth Providers](https://github.com/honojs/middleware/tree/main/packages/oauth-providers)
- [OIDC Auth](https://github.com/honojs/middleware/tree/main/packages/oidc-auth)
- [Firebase Auth](https://github.com/honojs/middleware/tree/main/packages/firebase-auth)
- [Verify RSA JWT (JWKS)](https://github.com/wataruoguchi/verify-rsa-jwt-cloudflare-worker)
- [Stytch Auth](https://github.com/honojs/middleware/tree/main/packages/stytch-auth)
### Validators
- [ArkType validator](https://github.com/honojs/middleware/tree/main/packages/arktype-validator)
- [Effect Schema Validator](https://github.com/honojs/middleware/tree/main/packages/effect-validator)
- [Standard Schema Validator](https://github.com/honojs/middleware/tree/main/packages/standard-validator)
- [TypeBox Validator](https://github.com/honojs/middleware/tree/main/packages/typebox-validator)
- [Typia Validator](https://github.com/honojs/middleware/tree/main/packages/typia-validator)
- [unknownutil Validator](https://github.com/ryoppippi/hono-unknownutil-validator)
- [Valibot Validator](https://github.com/honojs/middleware/tree/main/packages/valibot-validator)
- [Zod Validator](https://github.com/honojs/middleware/tree/main/packages/zod-validator)
### OpenAPI
- [Zod OpenAPI](https://github.com/honojs/middleware/tree/main/packages/zod-openapi)
- [Scalar API Reference](https://github.com/scalar/scalar/tree/main/integrations/hono)
- [Swagger UI](https://github.com/honojs/middleware/tree/main/packages/swagger-ui)
- [Hono OpenAPI](https://github.com/rhinobase/hono-openapi)
### Others
- [Bun Transpiler](https://github.com/honojs/middleware/tree/main/packages/bun-transpiler)
- [esbuild Transpiler](https://github.com/honojs/middleware/tree/main/packages/esbuild-transpiler)
- [Event Emitter](https://github.com/honojs/middleware/tree/main/packages/event-emitter)
- [GraphQL Server](https://github.com/honojs/middleware/tree/main/packages/graphql-server)
- [Hono Rate Limiter](https://github.com/rhinobase/hono-rate-limiter)
- [Node WebSocket Helper](https://github.com/honojs/middleware/tree/main/packages/node-ws)
- [Prometheus Metrics](https://github.com/honojs/middleware/tree/main/packages/prometheus)
- [Qwik City](https://github.com/honojs/middleware/tree/main/packages/qwik-city)
- [React Compatibility](https://github.com/honojs/middleware/tree/main/packages/react-compat)
- [React Renderer](https://github.com/honojs/middleware/tree/main/packages/react-renderer)
- [RONIN (Database)](https://github.com/ronin-co/hono-client)
- [Sentry](https://github.com/honojs/middleware/tree/main/packages/sentry)
- [tRPC Server](https://github.com/honojs/middleware/tree/main/packages/trpc-server)
- [Geo](https://github.com/ktkongtong/hono-geo-middleware/tree/main/packages/middleware)
- [Hono Simple DI](https://github.com/maou-shonen/hono-simple-DI)
- [Highlight.io](https://www.highlight.io/docs/getting-started/backend-sdk/js/hono)
- [Apitally (API monitoring & analytics)](https://docs.apitally.io/frameworks/hono)
- [Cap Checkpoint](https://capjs.js.org/guide/middleware/hono.html)
# Basic Auth Middleware
This middleware can apply Basic authentication to a specified path.
Implementing Basic authentication with Cloudflare Workers or other platforms is more complicated than it seems, but with this middleware, it's a breeze.
For more information about how the Basic auth scheme works under the hood, see the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme).
## Import
```ts
import { Hono } from 'hono'
import { basicAuth } from 'hono/basic-auth'
```
## Usage
```ts
const app = new Hono()
app.use(
'/auth/*',
basicAuth({
username: 'hono',
password: 'acoolproject',
})
)
app.get('/auth/page', (c) => {
return c.text('You are authorized')
})
```
To restrict to a specific route + method:
```ts
const app = new Hono()
app.get('/auth/page', (c) => {
return c.text('Viewing page')
})
app.delete(
'/auth/page',
basicAuth({ username: 'hono', password: 'acoolproject' }),
(c) => {
return c.text('Page deleted')
}
)
```
If you want to verify the user by yourself, specify the `verifyUser` option; returning `true` means it is accepted.
```ts
const app = new Hono()
app.use(
basicAuth({
verifyUser: (username, password, c) => {
return (
username === 'dynamic-user' && password === 'hono-password'
)
},
})
)
```
## Options
### username: `string`
The username of the user who is authenticating.
### password: `string`
The password value for the provided username to authenticate against.
### realm: `string`
The domain name of the realm, as part of the returned WWW-Authenticate challenge header. The default is `"Secure Area"`.
See more: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives
### hashFunction: `Function`
A function to handle hashing for safe comparison of passwords.
### verifyUser: `(username: string, password: string, c: Context) => boolean | Promise`
The function to verify the user.
### invalidUserMessage: `string | object | MessageFunction`
`MessageFunction` is `(c: Context) => string | object | Promise`. The custom message if the user is invalid.
## More Options
### ...users: `{ username: string, password: string }[]`
## Recipes
### Defining Multiple Users
This middleware also allows you to pass arbitrary parameters containing objects defining more `username` and `password` pairs.
```ts
app.use(
'/auth/*',
basicAuth(
{
username: 'hono',
password: 'acoolproject',
// Define other params in the first object
realm: 'www.example.com',
},
{
username: 'hono-admin',
password: 'super-secure',
// Cannot redefine other params here
},
{
username: 'hono-user-1',
password: 'a-secret',
// Or here
}
)
)
```
Or less hardcoded:
```ts
import { users } from '../config/users'
app.use(
'/auth/*',
basicAuth(
{
realm: 'www.example.com',
...users[0],
},
...users.slice(1)
)
)
```
# Bearer Auth Middleware
The Bearer Auth Middleware provides authentication by verifying an API token in the Request header.
The HTTP clients accessing the endpoint will add the `Authorization` header with `Bearer {token}` as the header value.
Using `curl` from the terminal, it would look like this:
```sh
curl -H 'Authorization: Bearer honoiscool' http://localhost:8787/auth/page
```
## Import
```ts
import { Hono } from 'hono'
import { bearerAuth } from 'hono/bearer-auth'
```
## Usage
> [!NOTE]
> Your `token` must match the regex `/[A-Za-z0-9._~+/-]+=*/`, otherwise a 400 error will be returned. Notably, this regex acommodates both URL-safe Base64- and standard Base64-encoded JWTs. This middleware does not require the bearer token to be a JWT, just that it matches the above regex.
```ts
const app = new Hono()
const token = 'honoiscool'
app.use('/api/*', bearerAuth({ token }))
app.get('/api/page', (c) => {
return c.json({ message: 'You are authorized' })
})
```
To restrict to a specific route + method:
```ts
const app = new Hono()
const token = 'honoiscool'
app.get('/api/page', (c) => {
return c.json({ message: 'Read posts' })
})
app.post('/api/page', bearerAuth({ token }), (c) => {
return c.json({ message: 'Created post!' }, 201)
})
```
To implement multiple tokens (E.g., any valid token can read but create/update/delete are restricted to a privileged token):
```ts
const app = new Hono()
const readToken = 'read'
const privilegedToken = 'read+write'
const privilegedMethods = ['POST', 'PUT', 'PATCH', 'DELETE']
app.on('GET', '/api/page/*', async (c, next) => {
// List of valid tokens
const bearer = bearerAuth({ token: [readToken, privilegedToken] })
return bearer(c, next)
})
app.on(privilegedMethods, '/api/page/*', async (c, next) => {
// Single valid privileged token
const bearer = bearerAuth({ token: privilegedToken })
return bearer(c, next)
})
// Define handlers for GET, POST, etc.
```
If you want to verify the value of the token yourself, specify the `verifyToken` option; returning `true` means it is accepted.
```ts
const app = new Hono()
app.use(
'/auth-verify-token/*',
bearerAuth({
verifyToken: async (token, c) => {
return token === 'dynamic-token'
},
})
)
```
## Options
### token: `string` | `string[]`
The string to validate the incoming bearer token against.
### realm: `string`
The domain name of the realm, as part of the returned WWW-Authenticate challenge header. The default is `""`.
See more: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives
### prefix: `string`
The prefix (or known as `schema`) for the Authorization header value. The default is `"Bearer"`.
### headerName: `string`
The header name. The default value is `Authorization`.
### hashFunction: `Function`
A function to handle hashing for safe comparison of authentication tokens.
### verifyToken: `(token: string, c: Context) => boolean | Promise`
The function to verify the token.
### noAuthenticationHeaderMessage: `string | object | MessageFunction`
`MessageFunction` is `(c: Context) => string | object | Promise`. The custom message if it does not have an authentication header.
### invalidAuthenticationHeaderMessage: `string | object | MessageFunction`
The custom message if the authentication header is invalid.
### invalidTokenMessage: `string | object | MessageFunction`
The custom message if the token is invalid.
# Body Limit Middleware
The Body Limit Middleware can limit the file size of the request body.
This middleware first uses the value of the `Content-Length` header in the request, if present.
If it is not set, it reads the body in the stream and executes an error handler if it is larger than the specified file size.
## Import
```ts
import { Hono } from 'hono'
import { bodyLimit } from 'hono/body-limit'
```
## Usage
```ts
const app = new Hono()
app.post(
'/upload',
bodyLimit({
maxSize: 50 * 1024, // 50kb
onError: (c) => {
return c.text('overflow :(', 413)
},
}),
async (c) => {
const body = await c.req.parseBody()
if (body['file'] instanceof File) {
console.log(`Got file sized: ${body['file'].size}`)
}
return c.text('pass :)')
}
)
```
## Options
### maxSize: `number`
The maximum file size of the file you want to limit. The default is `100 * 1024` - `100kb`.
### onError: `OnError`
The error handler to be invoked if the specified file size is exceeded.
## Usage with Bun for large requests
If the Body Limit Middleware is used explicitly to allow a request body larger than the default, it might be necessary to make changes to your `Bun.serve` configuration accordingly. [At the time of writing](https://github.com/oven-sh/bun/blob/f2cfa15e4ef9d730fc6842ad8b79fb7ab4c71cb9/packages/bun-types/bun.d.ts#L2191), `Bun.serve`'s default request body limit is 128MiB. If you set Hono's Body Limit Middleware to a value bigger than that, your requests will still fail and, additionally, the `onError` handler specified in the middleware will not be called. This is because `Bun.serve()` will set the status code to `413` and terminate the connection before passing the request to Hono.
If you want to accept requests larger than 128MiB with Hono and Bun, you need to set the limit for Bun as well:
```ts
export default {
port: process.env['PORT'] || 3000,
fetch: app.fetch,
maxRequestBodySize: 1024 * 1024 * 200, // your value here
}
```
or, depending on your setup:
```ts
Bun.serve({
fetch(req, server) {
return app.fetch(req, { ip: server.requestIP(req) })
},
maxRequestBodySize: 1024 * 1024 * 200, // your value here
})
```
# Cache Middleware
The Cache middleware uses the Web Standards' [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
The Cache middleware currently supports Cloudflare Workers projects using custom domains and Deno projects using [Deno 1.26+](https://github.com/denoland/deno/releases/tag/v1.26.0). Also available with Deno Deploy.
Cloudflare Workers respects the `Cache-Control` header and return cached responses. For details, refer to [Cache on Cloudflare Docs](https://developers.cloudflare.com/workers/runtime-apis/cache/). Deno does not respect headers, so if you need to update the cache, you will need to implement your own mechanism.
See [Usage](#usage) below for instructions on each platform.
## Import
```ts
import { Hono } from 'hono'
import { cache } from 'hono/cache'
```
## Usage
::: code-group
```ts [Cloudflare Workers]
app.get(
'*',
cache({
cacheName: 'my-app',
cacheControl: 'max-age=3600',
})
)
```
```ts [Deno]
// Must use `wait: true` for the Deno runtime
app.get(
'*',
cache({
cacheName: 'my-app',
cacheControl: 'max-age=3600',
wait: true,
})
)
```
:::
## Options
### cacheName: `string` | `(c: Context) => string` | `Promise`
The name of the cache. Can be used to store multiple caches with different identifiers.
### wait: `boolean`
A boolean indicating if Hono should wait for the Promise of the `cache.put` function to resolve before continuing with the request. _Required to be true for the Deno environment_. The default is `false`.
### cacheControl: `string`
A string of directives for the `Cache-Control` header. See the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) for more information. When this option is not provided, no `Cache-Control` header is added to requests.
### vary: `string` | `string[]`
Sets the `Vary` header in the response. If the original response header already contains a `Vary` header, the values are merged, removing any duplicates. Setting this to `*` will result in an error. For more details on the Vary header and its implications for caching strategies, refer to the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary).
### keyGenerator: `(c: Context) => string | Promise`
Generates keys for every request in the `cacheName` store. This can be used to cache data based on request parameters or context parameters. The default is `c.req.url`.
### cacheableStatusCodes: `number[]`
An array of status codes that should be cached. The default is `[200]`. Use this option to cache responses with specific status codes.
```ts
app.get(
'*',
cache({
cacheName: 'my-app',
cacheControl: 'max-age=3600',
cacheableStatusCodes: [200, 404, 412],
})
)
```
# Combine Middleware
Combine Middleware combines multiple middleware functions into a single middleware. It provides three functions:
- `some` - Runs only one of the given middleware.
- `every` - Runs all given middleware.
- `except` - Runs all given middleware only if a condition is not met.
## Import
```ts
import { Hono } from 'hono'
import { some, every, except } from 'hono/combine'
```
## Usage
Here's an example of complex access control rules using Combine Middleware.
```ts
import { Hono } from 'hono'
import { bearerAuth } from 'hono/bearer-auth'
import { getConnInfo } from 'hono/cloudflare-workers'
import { every, some } from 'hono/combine'
import { ipRestriction } from 'hono/ip-restriction'
import { rateLimit } from '@/my-rate-limit'
const app = new Hono()
app.use(
'*',
some(
every(
ipRestriction(getConnInfo, { allowList: ['192.168.0.2'] }),
bearerAuth({ token })
),
// If both conditions are met, rateLimit will not execute.
rateLimit()
)
)
app.get('/', (c) => c.text('Hello Hono!'))
```
### some
Runs the first middleware that returns true. Middleware is applied in order, and if any middleware exits successfully, subsequent middleware will not run.
```ts
import { some } from 'hono/combine'
import { bearerAuth } from 'hono/bearer-auth'
import { myRateLimit } from '@/rate-limit'
// If client has a valid token, skip rate limiting.
// Otherwise, apply rate limiting.
app.use(
'/api/*',
some(bearerAuth({ token }), myRateLimit({ limit: 100 }))
)
```
### every
Runs all middleware and stops if any of them fail. Middleware is applied in order, and if any middleware throws an error, subsequent middleware will not run.
```ts
import { some, every } from 'hono/combine'
import { bearerAuth } from 'hono/bearer-auth'
import { myCheckLocalNetwork } from '@/check-local-network'
import { myRateLimit } from '@/rate-limit'
// If client is in local network, skip authentication and rate limiting.
// Otherwise, apply authentication and rate limiting.
app.use(
'/api/*',
some(
myCheckLocalNetwork(),
every(bearerAuth({ token }), myRateLimit({ limit: 100 }))
)
)
```
### except
Runs all middleware except when the condition is met. You can pass a string or function as the condition. If multiple targets need to be matched, pass them as an array.
```ts
import { except } from 'hono/combine'
import { bearerAuth } from 'hono/bearer-auth'
// If client is accessing public API, skip authentication.
// Otherwise, require a valid token.
app.use('/api/*', except('/api/public/*', bearerAuth({ token })))
```
# Compress Middleware
This middleware compresses the response body, according to `Accept-Encoding` request header.
::: info
**Note**: On Cloudflare Workers and Deno Deploy, the response body will be compressed automatically, so there is no need to use this middleware.
**Bun**: This middleware uses `CompressionStream` which is not yet supported in bun.
:::
## Import
```ts
import { Hono } from 'hono'
import { compress } from 'hono/compress'
```
## Usage
```ts
const app = new Hono()
app.use(compress())
```
## Options
### encoding: `'gzip'` | `'deflate'`
The compression scheme to allow for response compression. Either `gzip` or `deflate`. If not defined, both are allowed and will be used based on the `Accept-Encoding` header. `gzip` is prioritized if this option is not provided and the client provides both in the `Accept-Encoding` header.
### threshold: `number`
The minimum size in bytes to compress. Defaults to 1024 bytes.
# Context Storage Middleware
The Context Storage Middleware stores the Hono `Context` in the `AsyncLocalStorage`, to make it globally accessible.
::: info
**Note** This middleware uses `AsyncLocalStorage`. The runtime should support it.
**Cloudflare Workers**: To enable `AsyncLocalStorage`, add the [`nodejs_compat` or `nodejs_als` flag](https://developers.cloudflare.com/workers/configuration/compatibility-dates/#nodejs-compatibility-flag) to your `wrangler.toml` file.
:::
## Import
```ts
import { Hono } from 'hono'
import { contextStorage, getContext } from 'hono/context-storage'
```
## Usage
The `getContext()` will return the current Context object if the `contextStorage()` is applied as a middleware.
```ts
type Env = {
Variables: {
message: string
}
}
const app = new Hono()
app.use(contextStorage())
app.use(async (c, next) => {
c.set('message', 'Hello!')
await next()
})
// You can access the variable outside the handler.
const getMessage = () => {
return getContext().var.message
}
app.get('/', (c) => {
return c.text(getMessage())
})
```
On Cloudflare Workers, you can access the bindings outside the handler.
```ts
type Env = {
Bindings: {
KV: KVNamespace
}
}
const app = new Hono()
app.use(contextStorage())
const setKV = (value: string) => {
return getContext().env.KV.put('key', value)
}
```
# CORS Middleware
There are many use cases of Cloudflare Workers as Web APIs and calling them from external front-end application.
For them we have to implement CORS, let's do this with middleware as well.
## Import
```ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
```
## Usage
```ts
const app = new Hono()
// CORS should be called before the route
app.use('/api/*', cors())
app.use(
'/api2/*',
cors({
origin: 'http://example.com',
allowHeaders: ['X-Custom-Header', 'Upgrade-Insecure-Requests'],
allowMethods: ['POST', 'GET', 'OPTIONS'],
exposeHeaders: ['Content-Length', 'X-Kuma-Revision'],
maxAge: 600,
credentials: true,
})
)
app.all('/api/abc', (c) => {
return c.json({ success: true })
})
app.all('/api2/abc', (c) => {
return c.json({ success: true })
})
```
Multiple origins:
```ts
app.use(
'/api3/*',
cors({
origin: ['https://example.com', 'https://example.org'],
})
)
// Or you can use "function"
app.use(
'/api4/*',
cors({
// `c` is a `Context` object
origin: (origin, c) => {
return origin.endsWith('.example.com')
? origin
: 'http://example.com'
},
})
)
```
Dynamic allowed methods based on origin:
```ts
app.use(
'/api5/*',
cors({
origin: (origin) =>
origin === 'https://example.com' ? origin : '*',
// `c` is a `Context` object
allowMethods: (origin, c) =>
origin === 'https://example.com'
? ['GET', 'HEAD', 'POST', 'PATCH', 'DELETE']
: ['GET', 'HEAD'],
})
)
```
## Options
### origin: `string` | `string[]` | `(origin:string, c:Context) => string`
The value of "_Access-Control-Allow-Origin_" CORS header. You can also pass the callback function like `origin: (origin) => (origin.endsWith('.example.com') ? origin : 'http://example.com')`. The default is `*`.
### allowMethods: `string[]` | `(origin:string, c:Context) => string[]`
The value of "_Access-Control-Allow-Methods_" CORS header. You can also pass a callback function to dynamically determine allowed methods based on the origin. The default is `['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']`.
### allowHeaders: `string[]`
The value of "_Access-Control-Allow-Headers_" CORS header. The default is `[]`.
### maxAge: `number`
The value of "_Access-Control-Max-Age_" CORS header.
### credentials: `boolean`
The value of "_Access-Control-Allow-Credentials_" CORS header.
### exposeHeaders: `string[]`
The value of "_Access-Control-Expose-Headers_" CORS header. The default is `[]`.
## Environment-dependent CORS configuration
If you want to adjust CORS configuration according to the execution environment, such as development or production, injecting values from environment variables is convenient as it eliminates the need for the application to be aware of its own execution environment. See the example below for clarification.
```ts
app.use('*', async (c, next) => {
const corsMiddlewareHandler = cors({
origin: c.env.CORS_ORIGIN,
})
return corsMiddlewareHandler(c, next)
})
```
## Using with Vite
When using Hono with Vite, you should disable Vite's built-in CORS feature by setting `server.cors` to `false` in your `vite.config.ts`. This prevents conflicts with Hono's CORS middleware.
```ts
// vite.config.ts
import { cloudflare } from '@cloudflare/vite-plugin'
import { defineConfig } from 'vite'
export default defineConfig({
server: {
cors: false, // disable Vite's built-in CORS setting
},
plugins: [cloudflare()],
})
```
# CSRF Protection
CSRF Protection Middleware prevents CSRF attacks by checking request headers.
This middleware protects against CSRF attacks such as submitting with a form element by comparing the value of the `Origin` header with the requested URL.
Old browsers that do not send `Origin` headers, or environments that use reverse proxies to remove `Origin` headers, may not work well. In such environments, use the other CSRF token methods.
## Import
```ts
import { Hono } from 'hono'
import { csrf } from 'hono/csrf'
```
## Usage
```ts
const app = new Hono()
app.use(csrf())
// Specifying origins with using `origin` option
// string
app.use(csrf({ origin: 'myapp.example.com' }))
// string[]
app.use(
csrf({
origin: ['myapp.example.com', 'development.myapp.example.com'],
})
)
// Function
// It is strongly recommended that the protocol be verified to ensure a match to `$`.
// You should *never* do a forward match.
app.use(
'*',
csrf({
origin: (origin) =>
/https:\/\/(\w+\.)?myapp\.example\.com$/.test(origin),
})
)
```
## Options
### origin: `string` | `string[]` | `Function`
Specify origins.
# ETag Middleware
Using this middleware, you can add ETag headers easily.
## Import
```ts
import { Hono } from 'hono'
import { etag } from 'hono/etag'
```
## Usage
```ts
const app = new Hono()
app.use('/etag/*', etag())
app.get('/etag/abc', (c) => {
return c.text('Hono is cool')
})
```
## The retained headers
The 304 Response must include the headers that would have been sent in an equivalent 200 OK response. The default headers are Cache-Control, Content-Location, Date, ETag, Expires, and Vary.
If you want to add the header that is sent, you can use `retainedHeaders` option and `RETAINED_304_HEADERS` strings array variable that includes the default headers:
```ts
import { etag, RETAINED_304_HEADERS } from 'hono/etag'
// ...
app.use(
'/etag/*',
etag({
retainedHeaders: ['x-message', ...RETAINED_304_HEADERS],
})
)
```
## Options
### weak: `boolean`
Define using or not using a [weak validation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests#weak_validation). If `true` is set, then `w/` is added to the prefix of the value. The default is `false`.
### retainedHeaders: `string[]`
The headers that you want to retain in the 304 Response.
### generateDigest: `(body: Uint8Array) => ArrayBuffer | Promise`
A custom digest generation function. By default, it uses `SHA-1`. This function is called with the response body as a `Uint8Array` and should return a hash as an `ArrayBuffer` or a Promise of one.
# IP Restriction Middleware
IP Restriction Middleware is middleware that limits access to resources based on the IP address of the user.
## Import
```ts
import { Hono } from 'hono'
import { ipRestriction } from 'hono/ip-restriction'
```
## Usage
For your application running on Bun, if you want to allow access only from local, you can write it as follows. Specify the rules you want to deny in the `denyList` and the rules you want to allow in the `allowList`.
```ts
import { Hono } from 'hono'
import { getConnInfo } from 'hono/bun'
import { ipRestriction } from 'hono/ip-restriction'
const app = new Hono()
app.use(
'*',
ipRestriction(getConnInfo, {
denyList: [],
allowList: ['127.0.0.1', '::1'],
})
)
app.get('/', (c) => c.text('Hello Hono!'))
```
Pass the `getConninfo` from the [ConnInfo helper](/docs/helpers/conninfo) appropriate for your environment as the first argument of `ipRestriction`. For example, for Deno, it would look like this:
```ts
import { getConnInfo } from 'hono/deno'
import { ipRestriction } from 'hono/ip-restriction'
//...
app.use(
'*',
ipRestriction(getConnInfo, {
// ...
})
)
```
## Rules
Follow the instructions below for writing rules.
### IPv4
- `192.168.2.0` - Static IP Address
- `192.168.2.0/24` - CIDR Notation
- `*` - ALL Addresses
### IPv6
- `::1` - Static IP Address
- `::1/10` - CIDR Notation
- `*` - ALL Addresses
## Error handling
To customize the error, return a `Response` in the third argument.
```ts
app.use(
'*',
ipRestriction(
getConnInfo,
{
denyList: ['192.168.2.0/24'],
},
async (remote, c) => {
return c.text(`Blocking access from ${remote.addr}`, 403)
}
)
)
```
# JSX Renderer Middleware
JSX Renderer Middleware allows you to set up the layout when rendering JSX with the `c.render()` function, without the need for using `c.setRenderer()`. Additionally, it enables access to instances of Context within components through the use of `useRequestContext()`.
## Import
```ts
import { Hono } from 'hono'
import { jsxRenderer, useRequestContext } from 'hono/jsx-renderer'
```
## Usage
```jsx
const app = new Hono()
app.get(
'/page/*',
jsxRenderer(({ children }) => {
return (
Menu
)
})
```
## Options
### docType: `boolean` | `string`
If you do not want to add a DOCTYPE at the beginning of the HTML, set the `docType` option to `false`.
```tsx
app.use(
'*',
jsxRenderer(
({ children }) => {
return (
{children}
)
},
{ docType: false }
)
)
```
And you can specify the DOCTYPE.
```tsx
app.use(
'*',
jsxRenderer(
({ children }) => {
return (
{children}
)
},
{
docType:
'',
}
)
)
```
### stream: `boolean` | `Record`
If you set it to `true` or provide a Record value, it will be rendered as a streaming response.
```tsx
const AsyncComponent = async () => {
await new Promise((r) => setTimeout(r, 1000)) // sleep 1s
return
{children}
)
},
{ stream: true }
)
)
app.get('/', (c) => {
return c.render(
loading...}>
)
})
```
If `true` is set, the following headers are added:
```ts
{
'Transfer-Encoding': 'chunked',
'Content-Type': 'text/html; charset=UTF-8',
'Content-Encoding': 'Identity'
}
```
You can customize the header values by specifying the Record values.
## Nested Layouts
The `Layout` component enables nesting the layouts.
```tsx
app.use(
jsxRenderer(({ children }) => {
return (
{children}
)
})
)
const blog = new Hono()
blog.use(
jsxRenderer(({ children, Layout }) => {
return (
{children}
)
})
)
app.route('/blog', blog)
```
## `useRequestContext()`
`useRequestContext()` returns an instance of Context.
```tsx
import { useRequestContext, jsxRenderer } from 'hono/jsx-renderer'
const app = new Hono()
app.use(jsxRenderer())
const RequestUrlBadge: FC = () => {
const c = useRequestContext()
return {c.req.url}
}
app.get('/page/info', (c) => {
return c.render(
You are accessing:
)
})
```
::: warning
You can't use `useRequestContext()` with the Deno's `precompile` JSX option. Use the `react-jsx`:
```json
"compilerOptions": {
"jsx": "precompile", // [!code --]
"jsx": "react-jsx", // [!code ++]
"jsxImportSource": "hono/jsx"
}
}
```
:::
## Extending `ContextRenderer`
By defining `ContextRenderer` as shown below, you can pass additional content to the renderer. This is handy, for instance, when you want to change the contents of the head tag depending on the page.
```tsx
declare module 'hono' {
interface ContextRenderer {
(
content: string | Promise,
props: { title: string }
): Response
}
}
const app = new Hono()
app.get(
'/page/*',
jsxRenderer(({ children, title }) => {
return (
{title}Menu
,
{
title: 'My favorites',
}
)
})
```
# JWK Auth Middleware
The JWK Auth Middleware authenticates requests by verifying tokens using JWK (JSON Web Key). It checks for an `Authorization` header and other configured sources, such as cookies, if specified. Specifically, it validates tokens using the provided `keys`, retrieves keys from `jwks_uri` if specified, and supports token extraction from cookies if the `cookie` option is set.
:::info
The Authorization header sent from the client must have a specified scheme.
Example: `Bearer my.token.value` or `Basic my.token.value`
:::
## Import
```ts
import { Hono } from 'hono'
import { jwk } from 'hono/jwk'
```
## Usage
```ts
const app = new Hono()
app.use(
'/auth/*',
jwk({
jwks_uri: `https://${backendServer}/.well-known/jwks.json`,
})
)
app.get('/auth/page', (c) => {
return c.text('You are authorized')
})
```
Get payload:
```ts
const app = new Hono()
app.use(
'/auth/*',
jwk({
jwks_uri: `https://${backendServer}/.well-known/jwks.json`,
})
)
app.get('/auth/page', (c) => {
const payload = c.get('jwtPayload')
return c.json(payload) // eg: { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
})
```
Anonymous access:
```ts
const app = new Hono()
app.use(
'/auth/*',
jwk({
jwks_uri: (c) =>
`https://${c.env.authServer}/.well-known/jwks.json`,
allow_anon: true,
})
)
app.get('/auth/page', (c) => {
const payload = c.get('jwtPayload')
return c.json(payload ?? { message: 'hello anon' })
})
```
## Options
### keys: `HonoJsonWebKey[] | (c: Context) => Promise`
The values of your public keys, or a function that returns them. The function receives the Context object.
### jwks_uri: `string` | `(c: Context) => Promise`
If this value is set, attempt to fetch JWKs from this URI, expecting a JSON response with `keys`, which are added to the provided `keys` option. You can also pass a callback function to dynamically determine the JWKS URI using the Context.
### allow_anon: `boolean`
If this value is set to `true`, requests without a valid token will be allowed to pass through the middleware. Use `c.get('jwtPayload')` to check if the request is authenticated. The default is `false`.
### cookie: `string`
If this value is set, then the value is retrieved from the cookie header using that value as a key, which is then validated as a token.
# JWT Auth Middleware
The JWT Auth Middleware provides authentication by verifying the token with JWT.
The middleware will check for an `Authorization` header if the `cookie` option is not set. You can customize the header name using the `headerName` option.
:::info
The Authorization header sent from the client must have a specified scheme.
Example: `Bearer my.token.value` or `Basic my.token.value`
:::
## Import
```ts
import { Hono } from 'hono'
import { jwt } from 'hono/jwt'
import type { JwtVariables } from 'hono/jwt'
```
## Usage
```ts
// Specify the variable types to infer the `c.get('jwtPayload')`:
type Variables = JwtVariables
const app = new Hono<{ Variables: Variables }>()
app.use(
'/auth/*',
jwt({
secret: 'it-is-very-secret',
})
)
app.get('/auth/page', (c) => {
return c.text('You are authorized')
})
```
Get payload:
```ts
const app = new Hono()
app.use(
'/auth/*',
jwt({
secret: 'it-is-very-secret',
})
)
app.get('/auth/page', (c) => {
const payload = c.get('jwtPayload')
return c.json(payload) // eg: { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
})
```
::: tip
`jwt()` is just a middleware function. If you want to use an environment variable (eg: `c.env.JWT_SECRET`), you can use it as follows:
```js
app.use('/auth/*', (c, next) => {
const jwtMiddleware = jwt({
secret: c.env.JWT_SECRET,
})
return jwtMiddleware(c, next)
})
```
:::
## Options
### secret: `string`
A value of your secret key.
### cookie: `string`
If this value is set, then the value is retrieved from the cookie header using that value as a key, which is then validated as a token.
### alg: `string`
An algorithm type that is used for verifying. The default is `HS256`.
Available types are `HS256` | `HS384` | `HS512` | `RS256` | `RS384` | `RS512` | `PS256` | `PS384` | `PS512` | `ES256` | `ES384` | `ES512` | `EdDSA`.
### headerName: `string`
The name of the header to look for the JWT token. The default is `Authorization`.
```ts
app.use(
'/auth/*',
jwt({
secret: 'it-is-very-secret',
headerName: 'x-custom-auth-header',
})
)
```
# Language Middleware
The Language Detector middleware automatically determines a user's preferred language (locale) from various sources and makes it available via `c.get('language')`. Detection strategies include query parameters, cookies, headers, and URL path segments. Perfect for internationalization (i18n) and locale-specific content.
## Import
```ts
import { Hono } from 'hono'
import { languageDetector } from 'hono/language'
```
## Basic Usage
Detect language from query string, cookie, and header (default order), with fallback to English:
```ts
const app = new Hono()
app.use(
languageDetector({
supportedLanguages: ['en', 'ar', 'ja'], // Must include fallback
fallbackLanguage: 'en', // Required
})
)
app.get('/', (c) => {
const lang = c.get('language')
return c.text(`Hello! Your language is ${lang}`)
})
```
### Client Examples
```sh
# Via path
curl http://localhost:8787/ar/home
# Via query parameter
curl http://localhost:8787/?lang=ar
# Via cookie
curl -H 'Cookie: language=ja' http://localhost:8787/
# Via header
curl -H 'Accept-Language: ar,en;q=0.9' http://localhost:8787/
```
## Default Configuration
```ts
export const DEFAULT_OPTIONS: DetectorOptions = {
order: ['querystring', 'cookie', 'header'],
lookupQueryString: 'lang',
lookupCookie: 'language',
lookupFromHeaderKey: 'accept-language',
lookupFromPathIndex: 0,
caches: ['cookie'],
ignoreCase: true,
fallbackLanguage: 'en',
supportedLanguages: ['en'],
cookieOptions: {
sameSite: 'Strict',
secure: true,
maxAge: 365 * 24 * 60 * 60,
httpOnly: true,
},
debug: false,
}
```
## Key Behaviors
### Detection Workflow
1. **Order**: Checks sources in this sequence by default:
- Query parameter (?lang=ar)
- Cookie (language=ar)
- Accept-Language header
2. **Caching**: Stores detected language in a cookie (1 year by default)
3. **Fallback**: Uses `fallbackLanguage` if no valid detection (must be in `supportedLanguages`)
## Advanced Configuration
### Custom Detection Order
Prioritize URL path detection (e.g., /en/about):
```ts
app.use(
languageDetector({
order: ['path', 'cookie', 'querystring', 'header'],
lookupFromPathIndex: 0, // /en/profile → index 0 = 'en'
supportedLanguages: ['en', 'ar'],
fallbackLanguage: 'en',
})
)
```
### Language Code Transformation
Normalize complex codes (e.g., en-US → en):
```ts
app.use(
languageDetector({
convertDetectedLanguage: (lang) => lang.split('-')[0],
supportedLanguages: ['en', 'ja'],
fallbackLanguage: 'en',
})
)
```
### Cookie Configuration
```ts
app.use(
languageDetector({
lookupCookie: 'app_lang',
caches: ['cookie'],
cookieOptions: {
path: '/', // Cookie path
sameSite: 'Lax', // Cookie same-site policy
secure: true, // Only send over HTTPS
maxAge: 86400 * 365, // 1 year expiration
httpOnly: true, // Not accessible via JavaScript
domain: '.example.com', // Optional: specific domain
},
})
)
```
To disable cookie caching:
```ts
languageDetector({
caches: false,
})
```
### Debugging
Log detection steps:
```ts
languageDetector({
debug: true, // Shows: "Detected from querystring: ar"
})
```
## Options Reference
### Basic Options
| Option | Type | Default | Required | Description |
| :------------------- | :--------------- | :------------------------------------ | :------- | :--------------------- |
| `supportedLanguages` | `string[]` | `['en']` | Yes | Allowed language codes |
| `fallbackLanguage` | `string` | `'en'` | Yes | Default language |
| `order` | `DetectorType[]` | `['querystring', 'cookie', 'header']` | No | Detection sequence |
| `debug` | `boolean` | `false` | No | Enable logging |
### Detection Options
| Option | Type | Default | Description |
| :-------------------- | :------- | :------------------ | :------------------- |
| `lookupQueryString` | `string` | `'lang'` | Query parameter name |
| `lookupCookie` | `string` | `'language'` | Cookie name |
| `lookupFromHeaderKey` | `string` | `'accept-language'` | Header name |
| `lookupFromPathIndex` | `number` | `0` | Path segment index |
### Cookie Options
| Option | Type | Default | Description |
| :----------------------- | :---------------------------- | :----------- | :------------------- |
| `caches` | `CacheType[] \| false` | `['cookie']` | Cache settings |
| `cookieOptions.path` | `string` | `'/'` | Cookie path |
| `cookieOptions.sameSite` | `'Strict' \| 'Lax' \| 'None'` | `'Strict'` | SameSite policy |
| `cookieOptions.secure` | `boolean` | `true` | HTTPS only |
| `cookieOptions.maxAge` | `number` | `31536000` | Expiration (seconds) |
| `cookieOptions.httpOnly` | `boolean` | `true` | JS accessibility |
| `cookieOptions.domain` | `string` | `undefined` | Cookie domain |
### Advanced Options
| Option | Type | Default | Description |
| :------------------------ | :------------------------- | :---------- | :------------------------ |
| `ignoreCase` | `boolean` | `true` | Case-insensitive matching |
| `convertDetectedLanguage` | `(lang: string) => string` | `undefined` | Language code transformer |
## Validation & Error Handling
- `fallbackLanguage` must be in `supportedLanguages` (throws error during setup)
- `lookupFromPathIndex` must be ≥ 0
- Invalid configurations throw errors during middleware initialization
- Failed detections silently use `fallbackLanguage`
## Common Recipes
### Path-Based Routing
```ts
app.get('/:lang/home', (c) => {
const lang = c.get('language') // 'en', 'ar', etc.
return c.json({ message: getLocalizedContent(lang) })
})
```
### Multiple Supported Languages
```ts
languageDetector({
supportedLanguages: ['en', 'en-GB', 'ar', 'ar-EG'],
convertDetectedLanguage: (lang) => lang.replace('_', '-'), // Normalize
})
```
# Logger Middleware
It's a simple logger.
## Import
```ts
import { Hono } from 'hono'
import { logger } from 'hono/logger'
```
## Usage
```ts
const app = new Hono()
app.use(logger())
app.get('/', (c) => c.text('Hello Hono!'))
```
## Logging Details
The Logger Middleware logs the following details for each request:
- **Incoming Request**: Logs the HTTP method, request path, and incoming request.
- **Outgoing Response**: Logs the HTTP method, request path, response status code, and request/response times.
- **Status Code Coloring**: Response status codes are color-coded for better visibility and quick identification of status categories. Different status code categories are represented by different colors.
- **Elapsed Time**: The time taken for the request/response cycle is logged in a human-readable format, either in milliseconds (ms) or seconds (s).
By using the Logger Middleware, you can easily monitor the flow of requests and responses in your Hono application and quickly identify any issues or performance bottlenecks.
You can also extend the middleware further by providing your own `PrintFunc` function for tailored logging behavior.
## PrintFunc
The Logger Middleware accepts an optional `PrintFunc` function as a parameter. This function allows you to customize the logger and add additional logs.
## Options
### fn: `PrintFunc(str: string, ...rest: string[])`
- `str`: Passed by the logger.
- `...rest`: Additional string props to be printed to console.
### Example
Setting up a custom `PrintFunc` function to the Logger Middleware:
```ts
export const customLogger = (message: string, ...rest: string[]) => {
console.log(message, ...rest)
}
app.use(logger(customLogger))
```
Setting up the custom logger in a route:
```ts
app.post('/blog', (c) => {
// Routing logic
customLogger('Blog saved:', `Path: ${blog.url},`, `ID: ${blog.id}`)
// Output
// <-- POST /blog
// Blog saved: Path: /blog/example, ID: 1
// --> POST /blog 201 93ms
// Return Context
})
```
# Method Override Middleware
This middleware executes the handler of the specified method, which is different from the actual method of the request, depending on the value of the form, header, or query, and returns its response.
## Import
```ts
import { Hono } from 'hono'
import { methodOverride } from 'hono/method-override'
```
## Usage
```ts
const app = new Hono()
// If no options are specified, the value of `_method` in the form,
// e.g. DELETE, is used as the method.
app.use('/posts', methodOverride({ app }))
app.delete('/posts', (c) => {
// ....
})
```
## For example
Since HTML forms cannot send a DELETE method, you can put the value `DELETE` in the property named `_method` and send it. And the handler for `app.delete()` will be executed.
The HTML form:
```html
```
The application:
```ts
import { methodOverride } from 'hono/method-override'
const app = new Hono()
app.use('/posts', methodOverride({ app }))
app.delete('/posts', () => {
// ...
})
```
You can change the default values or use the header value and query value:
```ts
app.use('/posts', methodOverride({ app, form: '_custom_name' }))
app.use(
'/posts',
methodOverride({ app, header: 'X-METHOD-OVERRIDE' })
)
app.use('/posts', methodOverride({ app, query: '_method' }))
```
## Options
### app: `Hono`
The instance of `Hono` is used in your application.
### form: `string`
Form key with a value containing the method name.
The default is `_method`.
### header: `boolean`
Header name with a value containing the method name.
### query: `boolean`
Query parameter key with a value containing the method name.
# Pretty JSON Middleware
Pretty JSON middleware enables "_JSON pretty print_" for JSON response body.
Adding `?pretty` to url query param, the JSON strings are prettified.
```js
// GET /
{"project":{"name":"Hono","repository":"https://github.com/honojs/hono"}}
```
will be:
```js
// GET /?pretty
{
"project": {
"name": "Hono",
"repository": "https://github.com/honojs/hono"
}
}
```
## Import
```ts
import { Hono } from 'hono'
import { prettyJSON } from 'hono/pretty-json'
```
## Usage
```ts
const app = new Hono()
app.use(prettyJSON()) // With options: prettyJSON({ space: 4 })
app.get('/', (c) => {
return c.json({ message: 'Hono!' })
})
```
## Options
### space: `number`
Number of spaces for indentation. The default is `2`.
### query: `string`
The name of the query string for applying. The default is `pretty`.
# Request ID Middleware
Request ID Middleware generates a unique ID for each request, which you can use in your handlers.
## Import
```ts
import { Hono } from 'hono'
import { requestId } from 'hono/request-id'
```
## Usage
You can access the Request ID through the `requestId` variable in the handlers and middleware to which the Request ID Middleware is applied.
```ts
const app = new Hono()
app.use('*', requestId())
app.get('/', (c) => {
return c.text(`Your request id is ${c.get('requestId')}`)
})
```
If you want to explicitly specify the type, import `RequestIdVariables` and pass it in the generics of `new Hono()`.
```ts
import type { RequestIdVariables } from 'hono/request-id'
const app = new Hono<{
Variables: RequestIdVariables
}>()
```
### Set Request ID
You set a custom request ID in the header (default: `X-Request-Id`), the middleware will use that value instead of generating a new one:
```ts
const app = new Hono()
app.use('*', requestId())
app.get('/', (c) => {
return c.text(`${c.get('requestId')}`)
})
const res = await app.request('/', {
headers: {
'X-Request-Id': 'your-custom-id',
},
})
console.log(await res.text()) // your-custom-id
```
If you want to disable this feature, set [`headerName` option](#headername-string) to an empty string.
## Options
### limitLength: `number`
The maximum length of the request ID. The default is `255`.
### headerName: `string`
The header name used for the request ID. The default is `X-Request-Id`.
### generator: `(c: Context) => string`
The request ID generation function. By default, it uses `crypto.randomUUID()`.
# Secure Headers Middleware
Secure Headers Middleware simplifies the setup of security headers. Inspired in part by the capabilities of Helmet, it allows you to control the activation and deactivation of specific security headers.
## Import
```ts
import { Hono } from 'hono'
import { secureHeaders } from 'hono/secure-headers'
```
## Usage
You can use the optimal settings by default.
```ts
const app = new Hono()
app.use(secureHeaders())
```
You can suppress unnecessary headers by setting them to false.
```ts
const app = new Hono()
app.use(
'*',
secureHeaders({
xFrameOptions: false,
xXssProtection: false,
})
)
```
You can override default header values using a string.
```ts
const app = new Hono()
app.use(
'*',
secureHeaders({
strictTransportSecurity:
'max-age=63072000; includeSubDomains; preload',
xFrameOptions: 'DENY',
xXssProtection: '1',
})
)
```
## Supported Options
Each option corresponds to the following Header Key-Value pairs.
| Option | Header | Value | Default |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ---------- |
| - | X-Powered-By | (Delete Header) | True |
| contentSecurityPolicy | [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting |
| contentSecurityPolicyReportOnly | [Content-Security-Policy-Report-Only](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting |
| crossOriginEmbedderPolicy | [Cross-Origin-Embedder-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy) | require-corp | **False** |
| crossOriginResourcePolicy | [Cross-Origin-Resource-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy) | same-origin | True |
| crossOriginOpenerPolicy | [Cross-Origin-Opener-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy) | same-origin | True |
| originAgentCluster | [Origin-Agent-Cluster](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin-Agent-Cluster) | ?1 | True |
| referrerPolicy | [Referrer-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) | no-referrer | True |
| reportingEndpoints | [Reporting-Endpoints](https://www.w3.org/TR/reporting-1/#header) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting |
| reportTo | [Report-To](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting |
| strictTransportSecurity | [Strict-Transport-Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) | max-age=15552000; includeSubDomains | True |
| xContentTypeOptions | [X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options) | nosniff | True |
| xDnsPrefetchControl | [X-DNS-Prefetch-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control) | off | True |
| xDownloadOptions | [X-Download-Options](https://learn.microsoft.com/en-us/archive/blogs/ie/ie8-security-part-v-comprehensive-protection#mime-handling-force-save) | noopen | True |
| xFrameOptions | [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) | SAMEORIGIN | True |
| xPermittedCrossDomainPolicies | [X-Permitted-Cross-Domain-Policies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Permitted-Cross-Domain-Policies) | none | True |
| xXssProtection | [X-XSS-Protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection) | 0 | True |
| permissionPolicy | [Permissions-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy) | Usage: [Setting Permission-Policy](#setting-permission-policy) | No Setting |
## Middleware Conflict
Please be cautious about the order of specification when dealing with middleware that manipulates the same header.
In this case, Secure-headers operates and the `x-powered-by` is removed:
```ts
const app = new Hono()
app.use(secureHeaders())
app.use(poweredBy())
```
In this case, Powered-By operates and the `x-powered-by` is added:
```ts
const app = new Hono()
app.use(poweredBy())
app.use(secureHeaders())
```
## Setting Content-Security-Policy
```ts
const app = new Hono()
app.use(
'/test',
secureHeaders({
reportingEndpoints: [
{
name: 'endpoint-1',
url: 'https://example.com/reports',
},
],
// -- or alternatively
// reportTo: [
// {
// group: 'endpoint-1',
// max_age: 10886400,
// endpoints: [{ url: 'https://example.com/reports' }],
// },
// ],
contentSecurityPolicy: {
defaultSrc: ["'self'"],
baseUri: ["'self'"],
childSrc: ["'self'"],
connectSrc: ["'self'"],
fontSrc: ["'self'", 'https:', 'data:'],
formAction: ["'self'"],
frameAncestors: ["'self'"],
frameSrc: ["'self'"],
imgSrc: ["'self'", 'data:'],
manifestSrc: ["'self'"],
mediaSrc: ["'self'"],
objectSrc: ["'none'"],
reportTo: 'endpoint-1',
sandbox: ['allow-same-origin', 'allow-scripts'],
scriptSrc: ["'self'"],
scriptSrcAttr: ["'none'"],
scriptSrcElem: ["'self'"],
styleSrc: ["'self'", 'https:', "'unsafe-inline'"],
styleSrcAttr: ['none'],
styleSrcElem: ["'self'", 'https:', "'unsafe-inline'"],
upgradeInsecureRequests: [],
workerSrc: ["'self'"],
},
})
)
```
### `nonce` attribute
You can add a [`nonce` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce) to a `script` or `style` element by adding the `NONCE` imported from `hono/secure-headers` to a `scriptSrc` or `styleSrc`:
```tsx
import { secureHeaders, NONCE } from 'hono/secure-headers'
import type { SecureHeadersVariables } from 'hono/secure-headers'
// Specify the variable types to infer the `c.get('secureHeadersNonce')`:
type Variables = SecureHeadersVariables
const app = new Hono<{ Variables: Variables }>()
// Set the pre-defined nonce value to `scriptSrc`:
app.get(
'*',
secureHeaders({
contentSecurityPolicy: {
scriptSrc: [NONCE, 'https://allowed1.example.com'],
},
})
)
// Get the value from `c.get('secureHeadersNonce')`:
app.get('/', (c) => {
return c.html(
{/** contents */}
)
})
```
If you want to generate the nonce value yourself, you can also specify a function as the following:
```tsx
const app = new Hono<{
Variables: { myNonce: string }
}>()
const myNonceGenerator: ContentSecurityPolicyOptionHandler = (c) => {
// This function is called on every request.
const nonce = Math.random().toString(36).slice(2)
c.set('myNonce', nonce)
return `'nonce-${nonce}'`
}
app.get(
'*',
secureHeaders({
contentSecurityPolicy: {
scriptSrc: [myNonceGenerator, 'https://allowed1.example.com'],
},
})
)
app.get('/', (c) => {
return c.html(
{/** contents */}
)
})
```
## Setting Permission-Policy
The Permission-Policy header allows you to control which features and APIs can be used in the browser. Here's an example of how to set it:
```ts
const app = new Hono()
app.use(
'*',
secureHeaders({
permissionsPolicy: {
fullscreen: ['self'], // fullscreen=(self)
bluetooth: ['none'], // bluetooth=(none)
payment: ['self', 'https://example.com'], // payment=(self "https://example.com")
syncXhr: [], // sync-xhr=()
camera: false, // camera=none
microphone: true, // microphone=*
geolocation: ['*'], // geolocation=*
usb: ['self', 'https://a.example.com', 'https://b.example.com'], // usb=(self "https://a.example.com" "https://b.example.com")
accelerometer: ['https://*.example.com'], // accelerometer=("https://*.example.com")
gyroscope: ['src'], // gyroscope=(src)
magnetometer: [
'https://a.example.com',
'https://b.example.com',
], // magnetometer=("https://a.example.com" "https://b.example.com")
},
})
)
```
# Timeout Middleware
The Timeout Middleware enables you to easily manage request timeouts in your application. It allows you to set a maximum duration for requests and optionally define custom error responses if the specified timeout is exceeded.
## Import
```ts
import { Hono } from 'hono'
import { timeout } from 'hono/timeout'
```
## Usage
Here's how to use the Timeout Middleware with both default and custom settings:
Default Settings:
```ts
const app = new Hono()
// Applying a 5-second timeout
app.use('/api', timeout(5000))
// Handling a route
app.get('/api/data', async (c) => {
// Your route handler logic
return c.json({ data: 'Your data here' })
})
```
Custom settings:
```ts
import { HTTPException } from 'hono/http-exception'
// Custom exception factory function
const customTimeoutException = (context) =>
new HTTPException(408, {
message: `Request timeout after waiting ${context.req.headers.get(
'Duration'
)} seconds. Please try again later.`,
})
// for Static Exception Message
// const customTimeoutException = new HTTPException(408, {
// message: 'Operation timed out. Please try again later.'
// });
// Applying a 1-minute timeout with a custom exception
app.use('/api/long-process', timeout(60000, customTimeoutException))
app.get('/api/long-process', async (c) => {
// Simulate a long process
await new Promise((resolve) => setTimeout(resolve, 61000))
return c.json({ data: 'This usually takes longer' })
})
```
## Notes
- The duration for the timeout can be specified in milliseconds. The middleware will automatically reject the promise and potentially throw an error if the specified duration is exceeded.
- The timeout middleware cannot be used with stream Thus, use `stream.close` and `setTimeout` together.
```ts
app.get('/sse', async (c) => {
let id = 0
let running = true
let timer: number | undefined
return streamSSE(c, async (stream) => {
timer = setTimeout(() => {
console.log('Stream timeout reached, closing stream')
stream.close()
}, 3000) as unknown as number
stream.onAbort(async () => {
console.log('Client closed connection')
running = false
clearTimeout(timer)
})
while (running) {
const message = `It is ${new Date().toISOString()}`
await stream.writeSSE({
data: message,
event: 'time-update',
id: String(id++),
})
await stream.sleep(1000)
}
})
})
```
## Middleware Conflicts
Be cautious about the order of middleware, especially when using error-handling or other timing-related middleware, as it might affect the behavior of this timeout middleware.
# Server-Timing Middleware
The [Server-Timing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing) Middleware provides
performance metrics in the response headers.
::: info
Note: On Cloudflare Workers, the timer metrics may not be accurate,
since [timers only show the time of last I/O](https://developers.cloudflare.com/workers/learning/security-model/#step-1-disallow-timers-and-multi-threading).
:::
## Import
```ts [npm]
import { Hono } from 'hono'
import { timing, setMetric, startTime, endTime } from 'hono/timing'
import type { TimingVariables } from 'hono/timing'
```
## Usage
```js
// Specify the variable types to infer the `c.get('metric')`:
type Variables = TimingVariables
const app = new Hono<{ Variables: Variables }>()
// add the middleware to your router
app.use(timing());
app.get('/', async (c) => {
// add custom metrics
setMetric(c, 'region', 'europe-west3')
// add custom metrics with timing, must be in milliseconds
setMetric(c, 'custom', 23.8, 'My custom Metric')
// start a new timer
startTime(c, 'db');
const data = await db.findMany(...);
// end the timer
endTime(c, 'db');
return c.json({ response: data });
});
```
### Conditionally enabled
```ts
const app = new Hono()
app.use(
'*',
timing({
// c: Context of the request
enabled: (c) => c.req.method === 'POST',
})
)
```
## Result

## Options
### total: `boolean`
Show the total response time. The default is `true`.
### enabled: `boolean` | `(c: Context) => boolean`
Whether timings should be added to the headers or not. The default is `true`.
### totalDescription: `boolean`
Description for the total response time. The default is `Total Response Time`.
### autoEnd: `boolean`
If `startTime()` should end automatically at the end of the request.
If disabled, not manually ended timers will not be shown.
### crossOrigin: `boolean` | `string` | `(c: Context) => boolean | string`
The origin this timings header should be readable.
- If false, only from current origin.
- If true, from all origin.
- If string, from this domain(s). Multiple domains must be separated with a comma.
The default is `false`. See more [docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin).
# Trailing Slash Middleware
This middleware handles Trailing Slash in the URL on a GET request.
`appendTrailingSlash` redirects the URL to which it added the Trailing Slash if the content was not found. Also, `trimTrailingSlash` will remove the Trailing Slash.
## Import
```ts
import { Hono } from 'hono'
import {
appendTrailingSlash,
trimTrailingSlash,
} from 'hono/trailing-slash'
```
## Usage
Example of redirecting a GET request of `/about/me` to `/about/me/`.
```ts
import { Hono } from 'hono'
import { appendTrailingSlash } from 'hono/trailing-slash'
const app = new Hono({ strict: true })
app.use(appendTrailingSlash())
app.get('/about/me/', (c) => c.text('With Trailing Slash'))
```
Example of redirecting a GET request of `/about/me/` to `/about/me`.
```ts
import { Hono } from 'hono'
import { trimTrailingSlash } from 'hono/trailing-slash'
const app = new Hono({ strict: true })
app.use(trimTrailingSlash())
app.get('/about/me', (c) => c.text('Without Trailing Slash'))
```
## Note
It will be enabled when the request method is `GET` and the response status is `404`.
# Accepts Helper
Accepts Helper helps to handle Accept headers in the Requests.
## Import
```ts
import { Hono } from 'hono'
import { accepts } from 'hono/accepts'
```
## `accepts()`
The `accepts()` function looks at the Accept header, such as Accept-Encoding and Accept-Language, and returns the proper value.
```ts
import { accepts } from 'hono/accepts'
app.get('/', (c) => {
const accept = accepts(c, {
header: 'Accept-Language',
supports: ['en', 'ja', 'zh'],
default: 'en',
})
return c.json({ lang: accept })
})
```
### `AcceptHeader` type
The definition of the `AcceptHeader` type is as follows.
```ts
export type AcceptHeader =
| 'Accept'
| 'Accept-Charset'
| 'Accept-Encoding'
| 'Accept-Language'
| 'Accept-Patch'
| 'Accept-Post'
| 'Accept-Ranges'
```
## Options
### header: `AcceptHeader`
The target accept header.
### supports: `string[]`
The header values which your application supports.
### default: `string`
The default values.
### match: `(accepts: Accept[], config: acceptsConfig) => string`
The custom match function.
# Adapter Helper
The Adapter Helper provides a seamless way to interact with various platforms through a unified interface.
## Import
```ts
import { Hono } from 'hono'
import { env, getRuntimeKey } from 'hono/adapter'
```
## `env()`
The `env()` function facilitates retrieving environment variables across different runtimes, extending beyond just Cloudflare Workers' Bindings. The value that can be retrieved with `env(c)` may be different for each runtimes.
```ts
import { env } from 'hono/adapter'
app.get('/env', (c) => {
// NAME is process.env.NAME on Node.js or Bun
// NAME is the value written in `wrangler.toml` on Cloudflare
const { NAME } = env<{ NAME: string }>(c)
return c.text(NAME)
})
```
Supported Runtimes, Serverless Platforms and Cloud Services:
- Cloudflare Workers
- `wrangler.toml`
- `wrangler.jsonc`
- Deno
- [`Deno.env`](https://docs.deno.com/runtime/manual/basics/env_variables)
- `.env` file
- Bun
- [`Bun.env`](https://bun.sh/guides/runtime/set-env)
- `process.env`
- Node.js
- `process.env`
- Vercel
- [Environment Variables on Vercel](https://vercel.com/docs/projects/environment-variables)
- AWS Lambda
- [Environment Variables on AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/samples-blank.html#samples-blank-architecture)
- Lambda@Edge\
Environment Variables on Lambda are [not supported](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/add-origin-custom-headers.html) by Lambda@Edge, you need to use [Lamdba@Edge event](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html) as an alternative.
- Fastly Compute\
On Fastly Compute, you can use the ConfigStore to manage user-defined data.
- Netlify\
On Netlify, you can use the [Netlify Contexts](https://docs.netlify.com/site-deploys/overview/#deploy-contexts) to manage user-defined data.
### Specify the runtime
You can specify the runtime to get environment variables by passing the runtime key as the second argument.
```ts
app.get('/env', (c) => {
const { NAME } = env<{ NAME: string }>(c, 'workerd')
return c.text(NAME)
})
```
## `getRuntimeKey()`
The `getRuntimeKey()` function returns the identifier of the current runtime.
```ts
app.get('/', (c) => {
if (getRuntimeKey() === 'workerd') {
return c.text('You are on Cloudflare')
} else if (getRuntimeKey() === 'bun') {
return c.text('You are on Bun')
}
...
})
```
### Available Runtimes Keys
Here are the available runtimes keys, unavailable runtime key runtimes may be supported and labeled as `other`, with some being inspired by [WinterCG's Runtime Keys](https://runtime-keys.proposal.wintercg.org/):
- `workerd` - Cloudflare Workers
- `deno`
- `bun`
- `node`
- `edge-light` - Vercel Edge Functions
- `fastly` - Fastly Compute
- `other` - Other unknown runtimes keys
# ConnInfo Helper
The ConnInfo Helper helps you to get the connection information. For example, you can get the client's remote address easily.
## Import
::: code-group
```ts [Cloudflare Workers]
import { Hono } from 'hono'
import { getConnInfo } from 'hono/cloudflare-workers'
```
```ts [Deno]
import { Hono } from 'hono'
import { getConnInfo } from 'hono/deno'
```
```ts [Bun]
import { Hono } from 'hono'
import { getConnInfo } from 'hono/bun'
```
```ts [Vercel]
import { Hono } from 'hono'
import { getConnInfo } from 'hono/vercel'
```
```ts [Lambda@Edge]
import { Hono } from 'hono'
import { getConnInfo } from 'hono/lambda-edge'
```
```ts [Node.js]
import { Hono } from 'hono'
import { getConnInfo } from '@hono/node-server/conninfo'
```
:::
## Usage
```ts
const app = new Hono()
app.get('/', (c) => {
const info = getConnInfo(c) // info is `ConnInfo`
return c.text(`Your remote address is ${info.remote.address}`)
})
```
## Type Definitions
The type definitions of the values that you can get from `getConnInfo()` are the following:
```ts
type AddressType = 'IPv6' | 'IPv4' | undefined
type NetAddrInfo = {
/**
* Transport protocol type
*/
transport?: 'tcp' | 'udp'
/**
* Transport port number
*/
port?: number
address?: string
addressType?: AddressType
} & (
| {
/**
* Host name such as IP Addr
*/
address: string
/**
* Host name type
*/
addressType: AddressType
}
| {}
)
/**
* HTTP Connection information
*/
interface ConnInfo {
/**
* Remote information
*/
remote: NetAddrInfo
}
```
# Cookie Helper
The Cookie Helper provides an easy interface to manage cookies, enabling developers to set, parse, and delete cookies seamlessly.
## Import
```ts
import { Hono } from 'hono'
import {
getCookie,
getSignedCookie,
setCookie,
setSignedCookie,
deleteCookie,
} from 'hono/cookie'
```
## Usage
### Regular cookies
```ts
app.get('/cookie', (c) => {
setCookie(c, 'cookie_name', 'cookie_value')
const yummyCookie = getCookie(c, 'cookie_name')
deleteCookie(c, 'cookie_name')
const allCookies = getCookie(c)
// ...
})
```
### Signed cookies
**NOTE**: Setting and retrieving signed cookies returns a Promise due to the async nature of the WebCrypto API, which is used to create HMAC SHA-256 signatures.
```ts
app.get('/signed-cookie', (c) => {
const secret = 'secret' // make sure it's a large enough string to be secure
await setSignedCookie(c, 'cookie_name0', 'cookie_value', secret)
const fortuneCookie = await getSignedCookie(
c,
secret,
'cookie_name0'
)
deleteCookie(c, 'cookie_name0')
// `getSignedCookie` will return `false` for a specified cookie if the signature was tampered with or is invalid
const allSignedCookies = await getSignedCookie(c, secret)
// ...
})
```
## Options
### `setCookie` & `setSignedCookie`
- domain: `string`
- expires: `Date`
- httpOnly: `boolean`
- maxAge: `number`
- path: `string`
- secure: `boolean`
- sameSite: `'Strict'` | `'Lax'` | `'None'`
- priority: `'Low' | 'Medium' | 'High'`
- prefix: `secure` | `'host'`
- partitioned: `boolean`
Example:
```ts
// Regular cookies
setCookie(c, 'great_cookie', 'banana', {
path: '/',
secure: true,
domain: 'example.com',
httpOnly: true,
maxAge: 1000,
expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),
sameSite: 'Strict',
})
// Signed cookies
await setSignedCookie(
c,
'fortune_cookie',
'lots-of-money',
'secret ingredient',
{
path: '/',
secure: true,
domain: 'example.com',
httpOnly: true,
maxAge: 1000,
expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),
sameSite: 'Strict',
}
)
```
### `deleteCookie`
- path: `string`
- secure: `boolean`
- domain: `string`
Example:
```ts
deleteCookie(c, 'banana', {
path: '/',
secure: true,
domain: 'example.com',
})
```
`deleteCookie` returns the deleted value:
```ts
const deletedCookie = deleteCookie(c, 'delicious_cookie')
```
## `__Secure-` and `__Host-` prefix
The Cookie helper supports `__Secure-` and `__Host-` prefix for cookies names.
If you want to verify if the cookie name has a prefix, specify the prefix option.
```ts
const securePrefixCookie = getCookie(c, 'yummy_cookie', 'secure')
const hostPrefixCookie = getCookie(c, 'yummy_cookie', 'host')
const securePrefixSignedCookie = await getSignedCookie(
c,
secret,
'fortune_cookie',
'secure'
)
const hostPrefixSignedCookie = await getSignedCookie(
c,
secret,
'fortune_cookie',
'host'
)
```
Also, if you wish to specify a prefix when setting the cookie, specify a value for the prefix option.
```ts
setCookie(c, 'delicious_cookie', 'macha', {
prefix: 'secure', // or `host`
})
await setSignedCookie(
c,
'delicious_cookie',
'macha',
'secret choco chips',
{
prefix: 'secure', // or `host`
}
)
```
## Following the best practices
A New Cookie RFC (a.k.a cookie-bis) and CHIPS include some best practices for Cookie settings that developers should follow.
- [RFC6265bis-13](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-13)
- `Max-Age`/`Expires` limitation
- `__Host-`/`__Secure-` prefix limitation
- [CHIPS-01](https://www.ietf.org/archive/id/draft-cutler-httpbis-partitioned-cookies-01.html)
- `Partitioned` limitation
Hono is following the best practices.
The cookie helper will throw an `Error` when parsing cookies under the following conditions:
- The cookie name starts with `__Secure-`, but `secure` option is not set.
- The cookie name starts with `__Host-`, but `secure` option is not set.
- The cookie name starts with `__Host-`, but `path` is not `/`.
- The cookie name starts with `__Host-`, but `domain` is set.
- The `maxAge` option value is greater than 400 days.
- The `expires` option value is 400 days later than the current time.
# css Helper
The css helper - `hono/css` - is Hono's built-in CSS in JS(X).
You can write CSS in JSX in a JavaScript template literal named `css`. The return value of `css` will be the class name, which is set to the value of the class attribute. The `` component will then contain the value of the CSS.
## Import
```ts
import { Hono } from 'hono'
import { css, cx, keyframes, Style } from 'hono/css'
```
## `css`
You can write CSS in the `css` template literal. In this case, it uses `headerClass` as a value of the `class` attribute. Don't forget to add `` as it contains the CSS content.
```ts{10,13}
app.get('/', (c) => {
const headerClass = css`
background-color: orange;
color: white;
padding: 1rem;
`
return c.html(
Hello!
)
})
```
You can style pseudo-classes like `:hover` by using the [nesting selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector), `&`:
```ts
const buttonClass = css`
background-color: #fff;
&:hover {
background-color: red;
}
`
```
### Extending
You can extend the CSS definition by embedding the class name.
```tsx
const baseClass = css`
color: white;
background-color: blue;
`
const header1Class = css`
${baseClass}
font-size: 3rem;
`
const header2Class = css`
${baseClass}
font-size: 2rem;
`
```
In addition, the syntax of `${baseClass} {}` enables nesting classes.
```tsx
const headerClass = css`
color: white;
background-color: blue;
`
const containerClass = css`
${headerClass} {
h1 {
font-size: 3rem;
}
}
`
return c.render(
Hello!
)
```
### Global styles
A pseudo-selector called `:-hono-global` allows you to define global styles.
```tsx
const globalClass = css`
:-hono-global {
html {
font-family: Arial, Helvetica, sans-serif;
}
}
`
return c.render(
Hello!
Today is a good day.
)
```
Or you can write CSS in the `` component with the `css` literal.
```tsx
export const renderer = jsxRenderer(({ children, title }) => {
return (
{title}
{children}
)
})
```
## `keyframes`
You can use `keyframes` to write the contents of `@keyframes`. In this case, `fadeInAnimation` will be the name of the animation
```tsx
const fadeInAnimation = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`
const headerClass = css`
animation-name: ${fadeInAnimation};
animation-duration: 2s;
`
const Header = () => Hello!
```
## `cx`
The `cx` composites the two class names.
```tsx
const buttonClass = css`
border-radius: 10px;
`
const primaryClass = css`
background: orange;
`
const Button = () => (
Click!
)
```
It can also compose simple strings.
```tsx
const Header = () => Hi
```
## Usage in combination with [Secure Headers](/docs/middleware/builtin/secure-headers) middleware
If you want to use the css helpers in combination with the [Secure Headers](/docs/middleware/builtin/secure-headers) middleware, you can add the [`nonce` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce) to the `` to avoid Content-Security-Policy caused by the css helpers.
```tsx{8,23}
import { secureHeaders, NONCE } from 'hono/secure-headers'
app.get(
'*',
secureHeaders({
contentSecurityPolicy: {
// Set the pre-defined nonce value to `styleSrc`:
styleSrc: [NONCE],
},
})
)
app.get('/', (c) => {
const headerClass = css`
background-color: orange;
color: white;
padding: 1rem;
`
return c.html(
{/* Set the `nonce` attribute on the css helpers `style` and `script` elements */}
Hello!
)
})
```
## Tips
If you use VS Code, you can use [vscode-styled-components](https://marketplace.visualstudio.com/items?itemName=styled-components.vscode-styled-components) for Syntax highlighting and IntelliSense for css tagged literals.

# Dev Helper
Dev Helper provides useful methods you can use in development.
```ts
import { Hono } from 'hono'
import { getRouterName, showRoutes } from 'hono/dev'
```
## `getRouterName()`
You can get the name of the currently used router with `getRouterName()`.
```ts
const app = new Hono()
// ...
console.log(getRouterName(app))
```
## `showRoutes()`
`showRoutes()` function displays the registered routes in your console.
Consider an application like the following:
```ts
const app = new Hono().basePath('/v1')
app.get('/posts', (c) => {
// ...
})
app.get('/posts/:id', (c) => {
// ...
})
app.post('/posts', (c) => {
// ...
})
showRoutes(app, {
verbose: true,
})
```
When this application starts running, the routes will be shown in your console as follows:
```txt
GET /v1/posts
GET /v1/posts/:id
POST /v1/posts
```
## Options
### verbose: `boolean`
When set to `true`, it displays verbose information.
### colorize: `boolean`
When set to `false`, the output will not be colored.
# Factory Helper
The Factory Helper provides useful functions for creating Hono's components such as Middleware. Sometimes it's difficult to set the proper TypeScript types, but this helper facilitates that.
## Import
```ts
import { Hono } from 'hono'
import { createFactory, createMiddleware } from 'hono/factory'
```
## `createFactory()`
`createFactory()` will create an instance of the Factory class.
```ts
import { createFactory } from 'hono/factory'
const factory = createFactory()
```
You can pass your Env types as Generics:
```ts
type Env = {
Variables: {
foo: string
}
}
const factory = createFactory()
```
### Options
### defaultAppOptions: `HonoOptions`
The default options to pass to the Hono application created by `createApp()`.
```ts
const factory = createFactory({
defaultAppOptions: { strict: false },
})
const app = factory.createApp() // `strict: false` is applied
```
## `createMiddleware()`
`createMiddleware()` is shortcut of `factory.createMiddleware()`.
This function will create your custom middleware.
```ts
const messageMiddleware = createMiddleware(async (c, next) => {
await next()
c.res.headers.set('X-Message', 'Good morning!')
})
```
Tip: If you want to get an argument like `message`, you can create it as a function like the following.
```ts
const messageMiddleware = (message: string) => {
return createMiddleware(async (c, next) => {
await next()
c.res.headers.set('X-Message', message)
})
}
app.use(messageMiddleware('Good evening!'))
```
## `factory.createHandlers()`
`createHandlers()` helps to define handlers in a different place than `app.get('/')`.
```ts
import { createFactory } from 'hono/factory'
import { logger } from 'hono/logger'
// ...
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)
```
## `factory.createApp()`
`createApp()` helps to create an instance of Hono with the proper types. If you use this method with `createFactory()`, you can avoid redundancy in the definition of the `Env` type.
If your application is like this, you have to set the `Env` in two places:
```ts
import { createMiddleware } from 'hono/factory'
type Env = {
Variables: {
myVar: string
}
}
// 1. Set the `Env` to `new Hono()`
const app = new Hono()
// 2. Set the `Env` to `createMiddleware()`
const mw = createMiddleware(async (c, next) => {
await next()
})
app.use(mw)
```
By using `createFactory()` and `createApp()`, you can set the `Env` only in one place.
```ts
import { createFactory } from 'hono/factory'
// ...
// Set the `Env` to `createFactory()`
const factory = createFactory()
const app = factory.createApp()
// factory also has `createMiddleware()`
const mw = factory.createMiddleware(async (c, next) => {
await next()
})
```
`createFactory()` can receive the `initApp` option to initialize an `app` created by `createApp()`. The following is an example that uses the option.
```ts
// factory-with-db.ts
type Env = {
Bindings: {
MY_DB: D1Database
}
Variables: {
db: DrizzleD1Database
}
}
export default createFactory({
initApp: (app) => {
app.use(async (c, next) => {
const db = drizzle(c.env.MY_DB)
c.set('db', db)
await next()
})
},
})
```
```ts
// crud.ts
import factoryWithDB from './factory-with-db'
const app = factoryWithDB.createApp()
app.post('/posts', (c) => {
c.var.db.insert()
// ...
})
```
# html Helper
The html Helper lets you write HTML in JavaScript template literal with a tag named `html`. Using `raw()`, the content will be rendered as is. You have to escape these strings by yourself.
## Import
```ts
import { Hono } from 'hono'
import { html, raw } from 'hono/html'
```
## `html`
```ts
const app = new Hono()
app.get('/:username', (c) => {
const { username } = c.req.param()
return c.html(
html`
Hello! ${username}!
`
)
})
```
### Insert snippets into JSX
Insert the inline script into JSX:
```tsx
app.get('/', (c) => {
return c.html(
Test Site
{html`
`}
Hello!
)
})
```
### Act as functional component
Since `html` returns an HtmlEscapedString, it can act as a fully functional component without using JSX.
#### Use `html` to speed up the process instead of `memo`
```typescript
const Footer = () => html`
`
```
### Receives props and embeds values
```typescript
interface SiteData {
title: string
description: string
image: string
children?: any
}
const Layout = (props: SiteData) => html`
${props.title}
${props.children}
`
const Content = (props: { siteData: SiteData; name: string }) => (
`)
})
```
## Tips
Thanks to these libraries, Visual Studio Code and vim also interprets template literals as HTML, allowing syntax highlighting and formatting to be applied.
-
-
# JWT Authentication Helper
This helper provides functions for encoding, decoding, signing, and verifying JSON Web Tokens (JWTs). JWTs are commonly used for authentication and authorization purposes in web applications. This helper offers robust JWT functionality with support for various cryptographic algorithms.
## Import
To use this helper, you can import it as follows:
```ts
import { decode, sign, verify } from 'hono/jwt'
```
::: info
[JWT Middleware](/docs/middleware/builtin/jwt) also import the `jwt` function from the `hono/jwt`.
:::
## `sign()`
This function generates a JWT token by encoding a payload and signing it using the specified algorithm and secret.
```ts
sign(
payload: unknown,
secret: string,
alg?: 'HS256';
): Promise;
```
### Example
```ts
import { sign } from 'hono/jwt'
const payload = {
sub: 'user123',
role: 'admin',
exp: Math.floor(Date.now() / 1000) + 60 * 5, // Token expires in 5 minutes
}
const secret = 'mySecretKey'
const token = await sign(payload, secret)
```
### Options
#### payload: `unknown`
The JWT payload to be signed. You can include other claims like in [Payload Validation](#payload-validation).
#### secret: `string`
The secret key used for JWT verification or signing.
#### alg: [AlgorithmTypes](#supported-algorithmtypes)
The algorithm used for JWT signing or verification. The default is HS256.
## `verify()`
This function checks if a JWT token is genuine and still valid. It ensures the token hasn't been altered and checks validity only if you added [Payload Validation](#payload-validation).
```ts
verify(
token: string,
secret: string,
alg?: 'HS256';
): Promise;
```
### Example
```ts
import { verify } from 'hono/jwt'
const tokenToVerify = 'token'
const secretKey = 'mySecretKey'
const decodedPayload = await verify(tokenToVerify, secretKey)
console.log(decodedPayload)
```
### Options
#### token: `string`
The JWT token to be verified.
#### secret: `string`
The secret key used for JWT verification or signing.
#### alg: [AlgorithmTypes](#supported-algorithmtypes)
The algorithm used for JWT signing or verification. The default is HS256.
## `decode()`
This function decodes a JWT token without performing signature verification. It extracts and returns the header and payload from the token.
```ts
decode(token: string): { header: any; payload: any };
```
### Example
```ts
import { decode } from 'hono/jwt'
// Decode the JWT token
const tokenToDecode =
'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAidXNlcjEyMyIsICJyb2xlIjogImFkbWluIn0.JxUwx6Ua1B0D1B0FtCrj72ok5cm1Pkmr_hL82sd7ELA'
const { header, payload } = decode(tokenToDecode)
console.log('Decoded Header:', header)
console.log('Decoded Payload:', payload)
```
### Options
#### token: `string`
The JWT token to be decoded.
> The `decode` function allows you to inspect the header and payload of a JWT token _**without**_ performing verification. This can be useful for debugging or extracting information from JWT tokens.
## Payload Validation
When verifying a JWT token, the following payload validations are performed:
- `exp`: The token is checked to ensure it has not expired.
- `nbf`: The token is checked to ensure it is not being used before a specified time.
- `iat`: The token is checked to ensure it is not issued in the future.
Please ensure that your JWT payload includes these fields, as an object, if you intend to perform these checks during verification.
## Custom Error Types
The module also defines custom error types to handle JWT-related errors.
- `JwtAlgorithmNotImplemented`: Indicates that the requested JWT algorithm is not implemented.
- `JwtTokenInvalid`: Indicates that the JWT token is invalid.
- `JwtTokenNotBefore`: Indicates that the token is being used before its valid date.
- `JwtTokenExpired`: Indicates that the token has expired.
- `JwtTokenIssuedAt`: Indicates that the "iat" claim in the token is incorrect.
- `JwtTokenSignatureMismatched`: Indicates a signature mismatch in the token.
## Supported AlgorithmTypes
The module supports the following JWT cryptographic algorithms:
- `HS256`: HMAC using SHA-256
- `HS384`: HMAC using SHA-384
- `HS512`: HMAC using SHA-512
- `RS256`: RSASSA-PKCS1-v1_5 using SHA-256
- `RS384`: RSASSA-PKCS1-v1_5 using SHA-384
- `RS512`: RSASSA-PKCS1-v1_5 using SHA-512
- `PS256`: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
- `PS384`: RSASSA-PSS using SHA-386 and MGF1 with SHA-386
- `PS512`: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
- `ES256`: ECDSA using P-256 and SHA-256
- `ES384`: ECDSA using P-384 and SHA-384
- `ES512`: ECDSA using P-521 and SHA-512
- `EdDSA`: EdDSA using Ed25519
# Proxy Helper
Proxy Helper provides useful functions when using Hono application as a (reverse) proxy.
## Import
```ts
import { Hono } from 'hono'
import { proxy } from 'hono/proxy'
```
## `proxy()`
`proxy()` is a `fetch()` API wrapper for proxy. The parameters and return value are the same as for `fetch()` (except for the proxy-specific options).
The `Accept-Encoding` header is replaced with an encoding that the current runtime can handle. Unnecessary response headers are deleted, and a `Response` object is returned that you can return as a response from the handler.
### Examples
Simple usage:
```ts
app.get('/proxy/:path', (c) => {
return proxy(`http://${originServer}/${c.req.param('path')}`)
})
```
Complicated usage:
```ts
app.get('/proxy/:path', async (c) => {
const res = await proxy(
`http://${originServer}/${c.req.param('path')}`,
{
headers: {
...c.req.header(), // optional, specify only when forwarding all the request data (including credentials) is necessary.
'X-Forwarded-For': '127.0.0.1',
'X-Forwarded-Host': c.req.header('host'),
Authorization: undefined, // do not propagate request headers contained in c.req.header('Authorization')
},
}
)
res.headers.delete('Set-Cookie')
return res
})
```
Or you can pass the `c.req` as a parameter.
```ts
app.all('/proxy/:path', (c) => {
return proxy(`http://${originServer}/${c.req.param('path')}`, {
...c.req, // optional, specify only when forwarding all the request data (including credentials) is necessary.
headers: {
...c.req.header(),
'X-Forwarded-For': '127.0.0.1',
'X-Forwarded-Host': c.req.header('host'),
Authorization: undefined, // do not propagate request headers contained in c.req.header('Authorization')
},
})
})
```
### `ProxyFetch`
The type of `proxy()` is defined as `ProxyFetch` and is as follows
```ts
interface ProxyRequestInit extends Omit {
raw?: Request
headers?:
| HeadersInit
| [string, string][]
| Record
| Record
}
interface ProxyFetch {
(
input: string | URL | Request,
init?: ProxyRequestInit
): Promise
}
```
# Route Helper
The Route Helper provides enhanced routing information for debugging and middleware development. It allows you to access detailed information about matched routes and the current route being processed.
## Import
```ts
import { Hono } from 'hono'
import {
matchedRoutes,
routePath,
baseRoutePath,
basePath,
} from 'hono/route'
```
## Usage
### Basic route information
```ts
const app = new Hono()
app.get('/posts/:id', (c) => {
const currentPath = routePath(c) // '/posts/:id'
const routes = matchedRoutes(c) // Array of matched routes
return c.json({
path: currentPath,
totalRoutes: routes.length,
})
})
```
### Working with sub-applications
```ts
const app = new Hono()
const apiApp = new Hono()
apiApp.get('/posts/:id', (c) => {
return c.json({
routePath: routePath(c), // '/posts/:id'
baseRoutePath: baseRoutePath(c), // '/api'
basePath: basePath(c), // '/api' (with actual params)
})
})
app.route('/api', apiApp)
```
## `matchedRoutes(c)`
Returns an array of all routes that matched the current request, including middleware.
```ts
app.all('/api/*', (c, next) => {
console.log('API middleware')
return next()
})
app.get('/api/users/:id', (c) => {
const routes = matchedRoutes(c)
// Returns: [
// { method: 'ALL', path: '/api/*', handler: [Function] },
// { method: 'GET', path: '/api/users/:id', handler: [Function] }
// ]
return c.json({ routes: routes.length })
})
```
## `routePath(c)`
Returns the route path pattern registered for the current handler.
```ts
app.get('/posts/:id', (c) => {
console.log(routePath(c)) // '/posts/:id'
return c.text('Post details')
})
```
## `baseRoutePath(c)`
Returns the base path pattern of the current route as specified in routing.
```ts
const subApp = new Hono()
subApp.get('/posts/:id', (c) => {
return c.text(baseRoutePath(c)) // '/:sub'
})
app.route('/:sub', subApp)
```
## `basePath(c)`
Returns the base path with embedded parameters from the actual request.
```ts
const subApp = new Hono()
subApp.get('/posts/:id', (c) => {
return c.text(basePath(c)) // '/api' (for request to '/api/posts/123')
})
app.route('/:sub', subApp)
```
# SSG Helper
SSG Helper generates a static site from your Hono application. It will retrieve the contents of registered routes and save them as static files.
## Usage
### Manual
If you have a simple Hono application like the following:
```tsx
// index.tsx
const app = new Hono()
app.get('/', (c) => c.html('Hello, World!'))
app.use('/about', async (c, next) => {
c.setRenderer((content, head) => {
return c.html(
{head.title ?? ''}
{content}
)
})
await next()
})
app.get('/about', (c) => {
return c.render('Hello!', { title: 'Hono SSG Page' })
})
export default app
```
For Node.js, create a build script like this:
```ts
// build.ts
import app from './index'
import { toSSG } from 'hono/ssg'
import fs from 'fs/promises'
toSSG(app, fs)
```
By executing the script, the files will be output as follows:
```bash
ls ./static
about.html index.html
```
### Vite Plugin
Using the `@hono/vite-ssg` Vite Plugin, you can easily handle the process.
For more details, see here:
https://github.com/honojs/vite-plugins/tree/main/packages/ssg
## toSSG
`toSSG` is the main function for generating static sites, taking an application and a filesystem module as arguments. It is based on the following:
### Input
The arguments for toSSG are specified in ToSSGInterface.
```ts
export interface ToSSGInterface {
(
app: Hono,
fsModule: FileSystemModule,
options?: ToSSGOptions
): Promise
}
```
- `app` specifies `new Hono()` with registered routes.
- `fs` specifies the following object, assuming `node:fs/promise`.
```ts
export interface FileSystemModule {
writeFile(path: string, data: string | Uint8Array): Promise
mkdir(
path: string,
options: { recursive: boolean }
): Promise
}
```
### Using adapters for Deno and Bun
If you want to use SSG on Deno or Bun, a `toSSG` function is provided for each file system.
For Deno:
```ts
import { toSSG } from 'hono/deno'
toSSG(app) // The second argument is an option typed `ToSSGOptions`.
```
For Bun:
```ts
import { toSSG } from 'hono/bun'
toSSG(app) // The second argument is an option typed `ToSSGOptions`.
```
### Options
Options are specified in the ToSSGOptions interface.
```ts
export interface ToSSGOptions {
dir?: string
concurrency?: number
beforeRequestHook?: BeforeRequestHook
afterResponseHook?: AfterResponseHook
afterGenerateHook?: AfterGenerateHook
extensionMap?: Record
plugins?: SSGPlugin[]
}
```
- `dir` is the output destination for Static files. The default value is `./static`.
- `concurrency` is the concurrent number of files to be generated at the same time. The default value is `2`.
- `extensionMap` is a map containing the `Content-Type` as a key and the string of the extension as a value. This is used to determine the file extension of the output file.
- `plugins` is an array of SSG plugins that extend the functionality of the static site generation process.
Each Hook and Plugin will be described later.
### Output
`toSSG` returns the result in the following Result type.
```ts
export interface ToSSGResult {
success: boolean
files: string[]
error?: Error
}
```
## Hook
You can customize the process of `toSSG` by specifying the following custom hooks in options.
```ts
export type BeforeRequestHook = (req: Request) => Request | false
export type AfterResponseHook = (res: Response) => Response | false
export type AfterGenerateHook = (
result: ToSSGResult
) => void | Promise
```
### BeforeRequestHook/AfterResponseHook
`toSSG` targets all routes registered in app, but if there are routes you want to exclude, you can filter them by specifying a Hook.
For example, if you want to output only GET requests, filter `req.method` in `beforeRequestHook`.
```ts
toSSG(app, fs, {
beforeRequestHook: (req) => {
if (req.method === 'GET') {
return req
}
return false
},
})
```
For example, if you want to output only when StatusCode is 200 or 500, filter `res.status` in `afterResponseHook`.
```ts
toSSG(app, fs, {
afterResponseHook: (res) => {
if (res.status === 200 || res.status === 500) {
return res
}
return false
},
})
```
### AfterGenerateHook
Use `afterGenerateHook` if you want to hook the result of `toSSG`.
```ts
toSSG(app, fs, {
afterGenerateHook: (result) => {
if (result.files) {
result.files.forEach((file) => console.log(file))
}
})
})
```
## Generate File
### Route and Filename
The following rules apply to the registered route information and the generated file name. The default `./static` behaves as follows:
- `/` -> `./static/index.html`
- `/path` -> `./static/path.html`
- `/path/` -> `./static/path/index.html`
### File Extension
The file extension depends on the `Content-Type` returned by each route. For example, responses from `c.html` are saved as `.html`.
If you want to customize the file extensions, set the `extensionMap` option.
```ts
import { toSSG, defaultExtensionMap } from 'hono/ssg'
// Save `application/x-html` content with `.html`
toSSG(app, fs, {
extensionMap: {
'application/x-html': 'html',
...defaultExtensionMap,
},
})
```
Note that paths ending with a slash are saved as index.ext regardless of the extension.
```ts
// save to ./static/html/index.html
app.get('/html/', (c) => c.html('html'))
// save to ./static/text/index.txt
app.get('/text/', (c) => c.text('text'))
```
## Middleware
Introducing built-in middleware that supports SSG.
### ssgParams
You can use an API like `generateStaticParams` of Next.js.
Example:
```ts
app.get(
'/shops/:id',
ssgParams(async () => {
const shops = await getShops()
return shops.map((shop) => ({ id: shop.id }))
}),
async (c) => {
const shop = await getShop(c.req.param('id'))
if (!shop) {
return c.notFound()
}
return c.render(
{shop.name}
)
}
)
```
### disableSSG
Routes with the `disableSSG` middleware set are excluded from static file generation by `toSSG`.
```ts
app.get('/api', disableSSG(), (c) => c.text('an-api'))
```
### onlySSG
Routes with the `onlySSG` middleware set will be overridden by `c.notFound()` after `toSSG` execution.
```ts
app.get('/static-page', onlySSG(), (c) => c.html(
Welcome to my site
))
```
## Plugins
Plugins allow you to extend the functionality of the static site generation process. It can perform custom actions at various hooks during generation.
### Plugin Interface
```ts
export interface SSGPlugin {
beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]
afterResponseHook?: AfterResponseHook | AfterResponseHook[]
afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[]
}
```
### Creating a plugin
Here's an example of creating a sitemap plugin that generates a `sitemap.xml` file:
```ts
// plugins.ts
import fs from 'node:fs/promises'
import path from 'node:path'
import type { SSGPlugin } from 'hono/ssg'
import { DEFAULT_OUTPUT_DIR } from 'hono/ssg'
export const sitemapPlugin = (baseURL: string): SSGPlugin => {
return {
afterGenerateHook: (result, fsModule, options) => {
const outputDir = options?.dir ?? DEFAULT_OUTPUT_DIR
const filePath = path.join(outputDir, 'sitemap.xml')
const urls = result.files.map((file) =>
new URL(file, baseURL).toString()
)
const siteMapText = `
${urls.map((url) => `${url}`).join('\n')}
`
fsModule.writeFile(filePath, siteMapText)
},
}
}
```
Applying the plugin:
```ts
import app from './index'
import { toSSG } from 'hono/ssg'
import { sitemapPlugin } from './plugins'
toSSG(app, fs, {
plugins: [sitemapPlugin('https://example.com')],
})
```
The plugin will generate a `sitemap.xml` file containing all the generated static files.
# Streaming Helper
The Streaming Helper provides methods for streaming responses.
## Import
```ts
import { Hono } from 'hono'
import { stream, streamText, streamSSE } from 'hono/streaming'
```
## `stream()`
It returns a simple streaming response as `Response` object.
```ts
app.get('/stream', (c) => {
return stream(c, async (stream) => {
// Write a process to be executed when aborted.
stream.onAbort(() => {
console.log('Aborted!')
})
// Write a Uint8Array.
await stream.write(new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]))
// Pipe a readable stream.
await stream.pipe(anotherReadableStream)
})
})
```
## `streamText()`
It returns a streaming response with `Content-Type:text/plain`, `Transfer-Encoding:chunked`, and `X-Content-Type-Options:nosniff` headers.
```ts
app.get('/streamText', (c) => {
return streamText(c, async (stream) => {
// Write a text with a new line ('\n').
await stream.writeln('Hello')
// Wait 1 second.
await stream.sleep(1000)
// Write a text without a new line.
await stream.write(`Hono!`)
})
})
```
::: warning
If you are developing an application for Cloudflare Workers, a streaming may not work well on Wrangler. If so, add `Identity` for `Content-Encoding` header.
```ts
app.get('/streamText', (c) => {
c.header('Content-Encoding', 'Identity')
return streamText(c, async (stream) => {
// ...
})
})
```
:::
## `streamSSE()`
It allows you to stream Server-Sent Events (SSE) seamlessly.
```ts
const app = new Hono()
let id = 0
app.get('/sse', async (c) => {
return streamSSE(c, async (stream) => {
while (true) {
const message = `It is ${new Date().toISOString()}`
await stream.writeSSE({
data: message,
event: 'time-update',
id: String(id++),
})
await stream.sleep(1000)
}
})
})
```
## Error Handling
The third argument of the streaming helper is an error handler.
This argument is optional, if you don't specify it, the error will be output as a console error.
```ts
app.get('/stream', (c) => {
return stream(
c,
async (stream) => {
// Write a process to be executed when aborted.
stream.onAbort(() => {
console.log('Aborted!')
})
// Write a Uint8Array.
await stream.write(
new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f])
)
// Pipe a readable stream.
await stream.pipe(anotherReadableStream)
},
(err, stream) => {
stream.writeln('An error occurred!')
console.error(err)
}
)
})
```
The stream will be automatically closed after the callbacks are executed.
::: warning
If the callback function of the streaming helper throws an error, the `onError` event of Hono will not be triggered.
`onError` is a hook to handle errors before the response is sent and overwrite the response. However, when the callback function is executed, the stream has already started, so it cannot be overwritten.
:::
# Testing Helper
The Testing Helper provides functions to make testing of Hono applications easier.
## Import
```ts
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
```
## `testClient()`
The `testClient()` function takes an instance of Hono as its first argument and returns an object typed according to your Hono application's routes, similar to the [Hono Client](/docs/guides/rpc#client). This allows you to call your defined routes in a type-safe manner with editor autocompletion within your tests.
**Important Note on Type Inference:**
For the `testClient` to correctly infer the types of your routes and provide autocompletion, **you must define your routes using chained methods directly on the `Hono` instance**.
The type inference relies on the type flowing through the chained `.get()`, `.post()`, etc., calls. If you define routes separately after creating the Hono instance (like the common pattern shown in the "Hello World" example: `const app = new Hono(); app.get(...)`), the `testClient` will not have the necessary type information for specific routes, and you won't get the type-safe client features.
**Example:**
This example works because the `.get()` method is chained directly onto the `new Hono()` call:
```ts
// index.ts
const app = new Hono().get('/search', (c) => {
const query = c.req.query('q')
return c.json({ query: query, results: ['result1', 'result2'] })
})
export default app
```
```ts
// index.test.ts
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
import { describe, it, expect } from 'vitest' // Or your preferred test runner
import app from './app'
describe('Search Endpoint', () => {
// Create the test client from the app instance
const client = testClient(app)
it('should return search results', async () => {
// Call the endpoint using the typed client
// Notice the type safety for query parameters (if defined in the route)
// and the direct access via .$get()
const res = await client.search.$get({
query: { q: 'hono' },
})
// Assertions
expect(res.status).toBe(200)
expect(await res.json()).toEqual({
query: 'hono',
results: ['result1', 'result2'],
})
})
})
```
To include headers in your test, pass them as the second parameter in the call. The second parameter can also take an `init` property as a `RequestInit` object, allowing you to set headers, method, body, etc. Learn more about the `init` property [here](/docs/guides/rpc#init-option).
```ts
// index.test.ts
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
import { describe, it, expect } from 'vitest' // Or your preferred test runner
import app from './app'
describe('Search Endpoint', () => {
// Create the test client from the app instance
const client = testClient(app)
it('should return search results', async () => {
// Include the token in the headers and set the content type
const token = 'this-is-a-very-clean-token'
const res = await client.search.$get(
{
query: { q: 'hono' },
},
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': `application/json`,
},
}
)
// Assertions
expect(res.status).toBe(200)
expect(await res.json()).toEqual({
query: 'hono',
results: ['result1', 'result2'],
})
})
})
```
# WebSocket Helper
WebSocket Helper is a helper for server-side WebSockets in Hono applications.
Currently Cloudflare Workers / Pages, Deno, and Bun adapters are available.
## Import
::: code-group
```ts [Cloudflare Workers]
import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/cloudflare-workers'
```
```ts [Deno]
import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/deno'
```
```ts [Bun]
import { Hono } from 'hono'
import { createBunWebSocket } from 'hono/bun'
import type { ServerWebSocket } from 'bun'
const { upgradeWebSocket, websocket } =
createBunWebSocket()
// ...
export default {
fetch: app.fetch,
websocket,
}
```
:::
If you use Node.js, you can use [@hono/node-ws](https://github.com/honojs/middleware/tree/main/packages/node-ws).
## `upgradeWebSocket()`
`upgradeWebSocket()` returns a handler for handling WebSocket.
```ts
const app = new Hono()
app.get(
'/ws',
upgradeWebSocket((c) => {
return {
onMessage(event, ws) {
console.log(`Message from client: ${event.data}`)
ws.send('Hello from server!')
},
onClose: () => {
console.log('Connection closed')
},
}
})
)
```
Available events:
- `onOpen` - Currently, Cloudflare Workers does not support it.
- `onMessage`
- `onClose`
- `onError`
::: warning
If you use middleware that modifies headers (e.g., applying CORS) on a route that uses WebSocket Helper, you may encounter an error saying you can't modify immutable headers. This is because `upgradeWebSocket()` also changes headers internally.
Therefore, please be cautious if you are using WebSocket Helper and middleware at the same time.
:::
## RPC-mode
Handlers defined with WebSocket Helper support RPC mode.
```ts
// server.ts
const wsApp = app.get(
'/ws',
upgradeWebSocket((c) => {
//...
})
)
export type WebSocketApp = typeof wsApp
// client.ts
const client = hc('http://localhost:8787')
const socket = client.ws.$ws() // A WebSocket object for a client
```
## Examples
See the examples using WebSocket Helper.
### Server and Client
```ts
// server.ts
import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/cloudflare-workers'
const app = new Hono().get(
'/ws',
upgradeWebSocket(() => {
return {
onMessage: (event) => {
console.log(event.data)
},
}
})
)
export default app
```
```ts
// client.ts
import { hc } from 'hono/client'
import type app from './server'
const client = hc('http://localhost:8787')
const ws = client.ws.$ws(0)
ws.addEventListener('open', () => {
setInterval(() => {
ws.send(new Date().toString())
}, 1000)
})
```
### Bun with JSX
```tsx
import { Hono } from 'hono'
import { createBunWebSocket } from 'hono/bun'
import { html } from 'hono/html'
const { upgradeWebSocket, websocket } = createBunWebSocket()
const app = new Hono()
app.get('/', (c) => {
return c.html(
{html`
`}
)
})
const ws = app.get(
'/ws',
upgradeWebSocket((c) => {
let intervalId
return {
onOpen(_event, ws) {
intervalId = setInterval(() => {
ws.send(new Date().toString())
}, 200)
},
onClose() {
clearInterval(intervalId)
},
}
})
)
export default {
fetch: app.fetch,
websocket,
}
```
# Best Practices
Hono is very flexible. You can write your app as you like.
However, there are best practices that are better to follow.
## Don't make "Controllers" when possible
When possible, you should not create "Ruby on Rails-like Controllers".
```ts
// 🙁
// A RoR-like Controller
const booksList = (c: Context) => {
return c.json('list books')
}
app.get('/books', booksList)
```
The issue is related to types. For example, the path parameter cannot be inferred in the Controller without writing complex generics.
```ts
// 🙁
// A RoR-like Controller
const bookPermalink = (c: Context) => {
const id = c.req.param('id') // Can't infer the path param
return c.json(`get ${id}`)
}
```
Therefore, you don't need to create RoR-like controllers and should write handlers directly after path definitions.
```ts
// 😃
app.get('/books/:id', (c) => {
const id = c.req.param('id') // Can infer the path param
return c.json(`get ${id}`)
})
```
## `factory.createHandlers()` in `hono/factory`
If you still want to create a RoR-like Controller, use `factory.createHandlers()` in [`hono/factory`](/docs/helpers/factory). If you use this, type inference will work correctly.
```ts
import { createFactory } from 'hono/factory'
import { logger } from 'hono/logger'
// ...
// 😃
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)
```
## Building a larger application
Use `app.route()` to build a larger application without creating "Ruby on Rails-like Controllers".
If your application has `/authors` and `/books` endpoints and you wish to separate files from `index.ts`, create `authors.ts` and `books.ts`.
```ts
// authors.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.json('list authors'))
app.post('/', (c) => c.json('create an author', 201))
app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
```ts
// books.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.json('list books'))
app.post('/', (c) => c.json('create a book', 201))
app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
Then, import them and mount on the paths `/authors` and `/books` with `app.route()`.
```ts
// index.ts
import { Hono } from 'hono'
import authors from './authors'
import books from './books'
const app = new Hono()
// 😃
app.route('/authors', authors)
app.route('/books', books)
export default app
```
### If you want to use RPC features
The code above works well for normal use cases.
However, if you want to use the `RPC` feature, you can get the correct type by chaining as follows.
```ts
// authors.ts
import { Hono } from 'hono'
const app = new Hono()
.get('/', (c) => c.json('list authors'))
.post('/', (c) => c.json('create an author', 201))
.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
If you pass the type of the `app` to `hc`, it will get the correct type.
```ts
import app from './authors'
import { hc } from 'hono/client'
// 😃
const client = hc('http://localhost') // Typed correctly
```
For more detailed information, please see [the RPC page](/docs/guides/rpc#using-rpc-with-larger-applications).
# Examples
See the [Examples section](/examples/).
# Frequently Asked Questions
This guide is a collection of frequently asked questions (FAQ) about Hono and how to resolve them.
## Is there an official Renovate config for Hono?
The Hono teams does not currently maintain [Renovate](https://github.com/renovatebot/renovate) Configuration.
Therefore, please use third-party renovate-config as follows.
In your `renovate.json` :
```json
// renovate.json
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>shinGangan/renovate-config-hono" // [!code ++]
]
}
```
see [renovate-config-hono](https://github.com/shinGangan/renovate-config-hono) repository for more details.
# Helpers
Helpers are available to assist in developing your application. Unlike middleware, they don't act as handlers, but rather provide useful functions.
For instance, here's how to use the [Cookie helper](/docs/helpers/cookie):
```ts
import { getCookie, setCookie } from 'hono/cookie'
const app = new Hono()
app.get('/cookie', (c) => {
const yummyCookie = getCookie(c, 'yummy_cookie')
// ...
setCookie(c, 'delicious_cookie', 'macha')
//
})
```
## Available Helpers
- [Accepts](/docs/helpers/accepts)
- [Adapter](/docs/helpers/adapter)
- [Cookie](/docs/helpers/cookie)
- [css](/docs/helpers/css)
- [Dev](/docs/helpers/dev)
- [Factory](/docs/helpers/factory)
- [html](/docs/helpers/html)
- [JWT](/docs/helpers/jwt)
- [SSG](/docs/helpers/ssg)
- [Streaming](/docs/helpers/streaming)
- [Testing](/docs/helpers/testing)
- [WebSocket](/docs/helpers/websocket)
# Client Components
`hono/jsx` supports not only server side but also client side. This means that it is possible to create an interactive UI that runs in the browser. We call it Client Components or `hono/jsx/dom`.
It is fast and very small. The counter program in `hono/jsx/dom` is only 2.8KB with Brotli compression. But, 47.8KB for React.
This section introduces Client Components-specific features.
## Counter example
Here is an example of a simple counter, the same code works as in React.
```tsx
import { useState } from 'hono/jsx'
import { render } from 'hono/jsx/dom'
function Counter() {
const [count, setCount] = useState(0)
return (
Count: {count}
)
}
function App() {
return (
)
}
const root = document.getElementById('root')
render(, root)
```
## `render()`
You can use `render()` to insert JSX components within a specified HTML element.
```tsx
render(, container)
```
## Hooks compatible with React
hono/jsx/dom has Hooks that are compatible or partially compatible with React. You can learn about these APIs by looking at [the React documentation](https://react.dev/reference/react/hooks).
- `useState()`
- `useEffect()`
- `useRef()`
- `useCallback()`
- `use()`
- `startTransition()`
- `useTransition()`
- `useDeferredValue()`
- `useMemo()`
- `useLayoutEffect()`
- `useReducer()`
- `useDebugValue()`
- `createElement()`
- `memo()`
- `isValidElement()`
- `useId()`
- `createRef()`
- `forwardRef()`
- `useImperativeHandle()`
- `useSyncExternalStore()`
- `useInsertionEffect()`
- `useFormStatus()`
- `useActionState()`
- `useOptimistic()`
## `startViewTransition()` family
The `startViewTransition()` family contains original hooks and functions to handle [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) easily. The followings are examples of how to use them.
### 1. An easiest example
You can write a transition using the `document.startViewTransition` shortly with the `startViewTransition()`.
```tsx
import { useState, startViewTransition } from 'hono/jsx'
import { css, Style } from 'hono/css'
export default function App() {
const [showLargeImage, setShowLargeImage] = useState(false)
return (
<>
{!showLargeImage ? (
) : (
)}
>
)
}
```
### 2. Using `viewTransition()` with `keyframes()`
The `viewTransition()` function allows you to get the unique `view-transition-name`.
You can use it with the `keyframes()`, The `::view-transition-old()` is converted to `::view-transition-old(${uniqueName))`.
```tsx
import { useState, startViewTransition } from 'hono/jsx'
import { viewTransition } from 'hono/jsx/dom/css'
import { css, keyframes, Style } from 'hono/css'
const rotate = keyframes`
from {
rotate: 0deg;
}
to {
rotate: 360deg;
}
`
export default function App() {
const [showLargeImage, setShowLargeImage] = useState(false)
const [transitionNameClass] = useState(() =>
viewTransition(css`
::view-transition-old() {
animation-name: ${rotate};
}
::view-transition-new() {
animation-name: ${rotate};
}
`)
)
return (
<>
{!showLargeImage ? (
) : (
)}
>
)
}
```
### 3. Using `useViewTransition`
If you want to change the style only during the animation. You can use `useViewTransition()`. This hook returns the `[boolean, (callback: () => void) => void]`, and they are the `isUpdating` flag and the `startViewTransition()` function.
When this hook is used, the Component is evaluated at the following two times.
- Inside the callback of a call to `startViewTransition()`.
- When [the `finish` promise becomes fulfilled](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition/finished)
```tsx
import { useState, useViewTransition } from 'hono/jsx'
import { viewTransition } from 'hono/jsx/dom/css'
import { css, keyframes, Style } from 'hono/css'
const rotate = keyframes`
from {
rotate: 0deg;
}
to {
rotate: 360deg;
}
`
export default function App() {
const [isUpdating, startViewTransition] = useViewTransition()
const [showLargeImage, setShowLargeImage] = useState(false)
const [transitionNameClass] = useState(() =>
viewTransition(css`
::view-transition-old() {
animation-name: ${rotate};
}
::view-transition-new() {
animation-name: ${rotate};
}
`)
)
return (
<>
{!showLargeImage ? (
) : (
)}
>
)
}
```
## The `hono/jsx/dom` runtime
There is a small JSX Runtime for Client Components. Using this will result in smaller bundled results than using `hono/jsx`. Specify `hono/jsx/dom` in `tsconfig.json`. For Deno, modify the deno.json.
```json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx/dom"
}
}
```
Alternatively, you can specify `hono/jsx/dom` in the esbuild transform options in `vite.config.ts`.
```ts
import { defineConfig } from 'vite'
export default defineConfig({
esbuild: {
jsxImportSource: 'hono/jsx/dom',
},
})
```
# JSX
You can write HTML with JSX syntax with `hono/jsx`.
Although `hono/jsx` works on the client, you will probably use it most often when rendering content on the server side. Here are some things related to JSX that are common to both server and client.
## Settings
To use JSX, modify the `tsconfig.json`:
`tsconfig.json`:
```json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
```
Alternatively, use the pragma directives:
```ts
/** @jsx jsx */
/** @jsxImportSource hono/jsx */
```
For Deno, you have to modify the `deno.json` instead of the `tsconfig.json`:
```json
{
"compilerOptions": {
"jsx": "precompile",
"jsxImportSource": "hono/jsx"
}
}
```
## Usage
:::info
If you are comming straight from the [Quick Start](/docs/#quick-start), the main file has a `.ts` extension - you need to change it to `.tsx` - otherwise you will not be able to run the application at all. You should additionally modify the `package.json` (or `deno.json` if you are using Deno) to reflect that change (e.g. instead of having `bun run --hot src/index.ts` in dev script, you should have `bun run --hot src/index.tsx`).
:::
`index.tsx`:
```tsx
import { Hono } from 'hono'
import type { FC } from 'hono/jsx'
const app = new Hono()
const Layout: FC = (props) => {
return (
{props.children}
)
}
const Top: FC<{ messages: string[] }> = (props: {
messages: string[]
}) => {
return (
Hello Hono!
{props.messages.map((message) => {
return
{message}!!
})}
)
}
app.get('/', (c) => {
const messages = ['Good Morning', 'Good Evening', 'Good Night']
return c.html()
})
export default app
```
## Fragment
Use Fragment to group multiple elements without adding extra nodes:
```tsx
import { Fragment } from 'hono/jsx'
const List = () => (
first child
second child
third child
)
```
Or you can write it with `<>>` if it set up properly.
```tsx
const List = () => (
<>
first child
second child
third child
>
)
```
## `PropsWithChildren`
You can use `PropsWithChildren` to correctly infer a child element in a function component.
```tsx
import { PropsWithChildren } from 'hono/jsx'
type Post = {
id: number
title: string
}
function Component({ title, children }: PropsWithChildren) {
return (
{title}
{children}
)
}
```
## Inserting Raw HTML
To directly insert HTML, use `dangerouslySetInnerHTML`:
```tsx
app.get('/foo', (c) => {
const inner = { __html: 'JSX · SSR' }
const Div =
})
```
## Memoization
Optimize your components by memoizing computed strings using `memo`:
```tsx
import { memo } from 'hono/jsx'
const Header = memo(() => Welcome to Hono)
const Footer = memo(() => )
const Layout = (
Hono is cool!
)
```
## Context
By using `useContext`, you can share data globally across any level of the Component tree without passing values through props.
```tsx
import type { FC } from 'hono/jsx'
import { createContext, useContext } from 'hono/jsx'
const themes = {
light: {
color: '#000000',
background: '#eeeeee',
},
dark: {
color: '#ffffff',
background: '#222222',
},
}
const ThemeContext = createContext(themes.light)
const Button: FC = () => {
const theme = useContext(ThemeContext)
return
}
const Toolbar: FC = () => {
return (
)
}
// ...
app.get('/', (c) => {
return c.html(
)
})
```
## Async Component
`hono/jsx` supports an Async Component, so you can use `async`/`await` in your component.
If you render it with `c.html()`, it will await automatically.
```tsx
const AsyncComponent = async () => {
await new Promise((r) => setTimeout(r, 1000)) // sleep 1s
return
Done!
}
app.get('/', (c) => {
return c.html(
)
})
```
## Suspense
The React-like `Suspense` feature is available.
If you wrap the async component with `Suspense`, the content in the fallback will be rendered first, and once the Promise is resolved, the awaited content will be displayed.
You can use it with `renderToReadableStream()`.
```tsx
import { renderToReadableStream, Suspense } from 'hono/jsx/streaming'
//...
app.get('/', (c) => {
const stream = renderToReadableStream(
loading...}>
)
return c.body(stream, {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
'Transfer-Encoding': 'chunked',
},
})
})
```
## ErrorBoundary
You can catch errors in child components using `ErrorBoundary`.
In the example below, it will show the content specified in `fallback` if an error occurs.
```tsx
function SyncComponent() {
throw new Error('Error')
return
Hello
}
app.get('/sync', async (c) => {
return c.html(
Out of Service}>
)
})
```
`ErrorBoundary` can also be used with async components and `Suspense`.
```tsx
async function AsyncComponent() {
await new Promise((resolve) => setTimeout(resolve, 2000))
throw new Error('Error')
return
Hello
}
app.get('/with-suspense', async (c) => {
return c.html(
Out of Service}>
Loading...}>
)
})
```
## StreamingContext
You can use `StreamingContext` to provide configuration for streaming components like `Suspense` and `ErrorBoundary`. This is useful for adding nonce values to script tags generated by these components for Content Security Policy (CSP).
```tsx
import { Suspense, StreamingContext } from 'hono/jsx/streaming'
// ...
app.get('/', (c) => {
const stream = renderToReadableStream(
Loading...}>
)
return c.body(stream, {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
'Transfer-Encoding': 'chunked',
'Content-Security-Policy':
"script-src 'nonce-random-nonce-value'",
},
})
})
```
The `scriptNonce` value will be automatically added to any `
) : (
)}