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_. | 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. ![Demo](/images/sc.gif) ## 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. ![SS](/images/ss.png) 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) ### 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) # 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`. # 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' }, }) ) ``` ## 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[]` The value of "_Access-Control-Allow-Methods_" CORS header. 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) }) ``` # 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
{children}
) }) ) app.get('/page/about', (c) => { return c.render(

About me!

) }) ``` ## 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
Hi!
} app.get( '*', jsxRenderer( ({ children }) => { return (

SSR Streaming

{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
{children}
) }) ) app.get('/page/favorites', (c) => { return c.render(
  • Eating sushi
  • Watching baseball games
, { 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 } }) ``` ## Options ### keys: `HonoJsonWebKey[] | (() => Promise)` The values of your public keys, or a function that returns them. ### jwks_uri: `string` 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. ### 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. :::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`. # 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 }>() ``` ## 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/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://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Download-Options) | 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 */} `} 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`
My Address...
` ``` ### 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 }) => (

Hello {props.name}

) app.get('/', (c) => { const props = { name: 'World', siteData: { title: 'Hello <> World', description: 'This is a description', image: 'https://example.com/image.png', }, } return c.html() }) ``` ## `raw()` ```ts app.get('/', (c) => { const name = 'John "Johnny" Smith' return c.html(html`

I'm ${raw(name)}.

`) }) ``` ## 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 } ``` # 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 } ``` - `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. Each Hook 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

)) ``` # 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()` takes an instance of Hono as its first argument and returns an object of the [Hono Client](/docs/guides/rpc#client). By using this, you can define your request with the editor completion. ```ts import { testClient } from 'hono/testing' it('test', async () => { const app = new Hono().get('/search', (c) => c.json({ hello: 'world' }) ) const res = await testClient(app).search.$get() expect(await res.json()).toEqual({ hello: 'world' }) }) ``` # 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' const { upgradeWebSocket, websocket } = createBunWebSocket() const app = new Hono() app.get('/', (c) => { return c.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 ( <>