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. ![A gif showing a hono app being created quickly with fast iteration.](/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. ![A screenshot showing Hono having proper literal typing when URL parameters. The URL "/entry/:date/:id" allows for request parameters to be "date" or "id"](/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). # 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'; issuer?: string | RegExp; ): Promise; ``` ### Example ```ts import { verify } from 'hono/jwt' const tokenToVerify = 'token' const secretKey = 'mySecretKey' const decodedPayload = await verify(tokenToVerify, secretKey, 'HS256') 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. #### issuer: `string | RegExp` The expected issuer used for JWT verification. ## `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. - `iss`: The token is checked to ensure it has been issued by a trusted issuer. 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. - `JwtTokenIssuer`: Indicates that the "iss" 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 # 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 { deleteCookie, getCookie, getSignedCookie, setCookie, setSignedCookie, generateCookie, generateSignedCookie, } 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) // ... }) ``` ### Cookie Generation `generateCookie` and `generateSignedCookie` functions allow you to create cookie strings directly without setting them in the response headers. #### `generateCookie` ```ts // Basic cookie generation const cookie = generateCookie('delicious_cookie', 'macha') // Returns: 'delicious_cookie=macha; Path=/' // Cookie with options const cookie = generateCookie('delicious_cookie', 'macha', { path: '/', secure: true, httpOnly: true, domain: 'example.com', }) ``` #### `generateSignedCookie` ```ts // Basic signed cookie generation const signedCookie = await generateSignedCookie( 'delicious_cookie', 'macha', 'secret chocolate chips' ) // Signed cookie with options const signedCookie = await generateSignedCookie( 'delicious_cookie', 'macha', 'secret chocolate chips', { path: '/', secure: true, httpOnly: true, } ) ``` **Note**: Unlike `setCookie` and `setSignedCookie`, these functions only generate the cookie strings. You need to manually set them in headers if needed. ## 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. # 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`
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. - - # 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()` 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()` 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') }) ``` ### Using with index parameter You can optionally pass an index parameter to get the route path at a specific position, similar to `Array.prototype.at()`. ```ts app.all('/api/*', (c, next) => { return next() }) app.get('/api/users/:id', (c) => { console.log(routePath(c, 0)) // '/api/*' (first matched route) console.log(routePath(c, -1)) // '/api/users/:id' (last matched route) return c.text('User details') }) ``` ## `baseRoutePath()` 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) ``` ### Using with index parameter You can optionally pass an index parameter to get the base route path at a specific position, similar to `Array.prototype.at()`. ```ts app.all('/api/*', (c, next) => { return next() }) const subApp = new Hono() subApp.get('/users/:id', (c) => { console.log(baseRoutePath(c, 0)) // '/' (first matched route) console.log(baseRoutePath(c, -1)) // '/api' (last matched route) return c.text('User details') }) app.route('/api', subApp) ``` ## `basePath()` 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) ``` # 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() // ... }) ``` # 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'], }) }) }) ``` # 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. ::: # 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 [AWS Lambda] import { Hono } from 'hono' import { getConnInfo } from 'hono/aws-lambda' ``` ```ts [Cloudflare Pages] import { Hono } from 'hono' import { getConnInfo } from 'hono/cloudflare-pages' ``` ```ts [Netlify] import { Hono } from 'hono' import { getConnInfo } from 'hono/netlify' ``` ```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 } ``` # 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. # 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. # 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 removed, and a `Response` object is returned that can be sent 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') }, }) }) ``` You can override the default global `fetch` function with the `customFetch` option: ```ts app.get('/proxy', (c) => { return proxy('https://example.com/', { customFetch, }) }) ``` ### Connection Header Processing By default, `proxy()` ignores the `Connection` header to prevent Hop-by-Hop Header Injection attacks. You can enable strict RFC 9110 compliance with the `strictConnectionProcessing` option: ```ts // Default behavior (recommended for untrusted clients) app.get('/proxy/:path', (c) => { return proxy(`http://${originServer}/${c.req.param('path')}`, c.req) }) // Strict RFC 9110 compliance (use only in trusted environments) app.get('/internal-proxy/:path', (c) => { return proxy(`http://${internalServer}/${c.req.param('path')}`, { ...c.req, strictConnectionProcessing: true, }) }) ``` ### `ProxyFetch` The type of `proxy()` is defined as `ProxyFetch` and is as follows ```ts interface ProxyRequestInit extends Omit { raw?: Request customFetch?: (request: Request) => Promise strictConnectionProcessing?: boolean 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) => { return c.html(

{content}

) }) await next() }) app.get('/about', (c) => { return c.render( <> Hono SSG PageHello! ) }) 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 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. ### Output `toSSG` returns the result in the following Result type. ```ts export interface ToSSGResult { success: boolean files: string[] error?: Error } ``` ## 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}

) } ) ``` ### isSSGContext `isSSGContext` is a helper function that returns `true` if the current application is running within the SSG context triggered by `toSSG`. ```ts app.get('/page', (c) => { if (isSSGContext(c)) { return c.text('This is generated by SSG') } return c.text('This is served dynamically') }) ``` ### 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. They use hooks to customize the generation process at different stages. ### Default Plugin By default, `toSSG` uses `defaultPlugin` which skips non-200 status responses (like redirects, errors, or 404s). This prevents generating files for unsuccessful responses. ```ts import { toSSG, defaultPlugin } from 'hono/ssg' // defaultPlugin is automatically applied when no plugins specified toSSG(app, fs) // Equivalent to: toSSG(app, fs, { plugins: [defaultPlugin] }) ``` If you specify custom plugins, `defaultPlugin` is **not** automatically included. To keep the default behavior while adding custom plugins, explicitly include it: ```ts toSSG(app, fs, { plugins: [defaultPlugin, myCustomPlugin], }) ``` ### Redirect Plugin The `redirectPlugin` generates HTML redirect pages for routes that return HTTP redirect responses (301, 302, 303, 307, 308). The generated HTML includes a `` tag and a canonical link. ```ts import { toSSG, redirectPlugin, defaultPlugin } from 'hono/ssg' toSSG(app, fs, { plugins: [redirectPlugin(), defaultPlugin()], }) ``` For example, if your app has: ```ts app.get('/old', (c) => c.redirect('/new')) ``` The `redirectPlugin` will generate an HTML file at `/old.html` with a meta refresh redirect to `/new`. > [!NOTE] > When used with `defaultPlugin`, place `redirectPlugin` **before** `defaultPlugin`. Since `defaultPlugin` skips non-200 responses, placing it first would prevent `redirectPlugin` from processing redirect responses. ### Hook Types Plugins can use the following hooks to customize the `toSSG` process: ```ts export type BeforeRequestHook = (req: Request) => Request | false export type AfterResponseHook = (res: Response) => Response | false export type AfterGenerateHook = ( result: ToSSGResult ) => void | Promise ``` - **BeforeRequestHook**: Called before processing each request. Return `false` to skip the route. - **AfterResponseHook**: Called after receiving each response. Return `false` to skip file generation. - **AfterGenerateHook**: Called after the entire generation process completes. ### Plugin Interface ```ts export interface SSGPlugin { beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[] afterResponseHook?: AfterResponseHook | AfterResponseHook[] afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[] } ``` ### Basic Plugin Examples Filter only GET requests: ```ts const getOnlyPlugin: SSGPlugin = { beforeRequestHook: (req) => { if (req.method === 'GET') { return req } return false }, } ``` Filter by status code: ```ts const statusFilterPlugin: SSGPlugin = { afterResponseHook: (res) => { if (res.status === 200 || res.status === 500) { return res } return false }, } ``` Log generated files: ```ts const logFilesPlugin: SSGPlugin = { afterGenerateHook: (result) => { if (result.files) { result.files.forEach((file) => console.log(file)) } }, } ``` ### Advanced Plugin Example 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 plugins: ```ts import app from './index' import { toSSG } from 'hono/ssg' import { sitemapPlugin } from './plugins' toSSG(app, fs, { plugins: [ getOnlyPlugin, statusFilterPlugin, logFilesPlugin, sitemapPlugin('https://example.com'), ], }) ``` # 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.com/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 # WebSocket Helper WebSocket Helper is a helper for server-side WebSockets in Hono applications. Currently Cloudflare Workers / Pages, Deno, Bun, and Node.js 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 { upgradeWebSocket, websocket } from 'hono/bun' // ... export default { fetch: app.fetch, websocket, } ``` ```ts [Node.js] import { serve, upgradeWebSocket } from '@hono/node-server' import { Hono } from 'hono' import { WebSocketServer } from 'ws' ``` ::: On Node.js, WebSocket support is built into `@hono/node-server`. To enable it, install `ws` and, if you use TypeScript, `@types/ws`. Then create a `WebSocketServer` with `{ noServer: true }` and pass it to `serve()` via the `websocket` option. `@hono/node-ws` is deprecated. ## `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 { upgradeWebSocket, websocket } from 'hono/bun' import { html } from 'hono/html' 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, } ``` ### Node.js ```ts import { serve, upgradeWebSocket } from '@hono/node-server' import { Hono } from 'hono' import { WebSocketServer } from 'ws' const app = new Hono() app.get( '/ws', upgradeWebSocket(() => ({ onMessage(event, ws) { ws.send(event.data) }, })) ) const wss = new WebSocketServer({ noServer: true }) serve({ fetch: app.fetch, websocket: { server: wss }, }) ``` # 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 ` {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 `