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). # Middleware We call the primitive that returns `Response` as "Handler". "Middleware" is executed before and after the Handler and handles the `Request` and `Response`. It's like an onion structure. ![](/images/onion.png) For example, we can write the middleware to add the "X-Response-Time" header as follows. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.use(async (c, next) => { const start = performance.now() await next() const end = performance.now() c.res.headers.set('X-Response-Time', `${end - start}`) }) ``` With this simple method, we can write our own custom middleware and we can use the built-in or third party middleware. # Web Standards Hono uses only **Web Standards** like Fetch. They were originally used in the `fetch` function and consist of basic objects that handle HTTP requests and responses. In addition to `Requests` and `Responses`, there are `URL`, `URLSearchParam`, `Headers` and others. Cloudflare Workers, Deno, and Bun also are built upon Web Standards. For example, a server that returns "Hello World" could be written as below. This could run on Cloudflare Workers and Bun. ```ts twoslash export default { async fetch() { return new Response('Hello World') }, } ``` Hono uses only Web Standards, which means that Hono can run on any runtime that supports them. In addition, we have a Node.js adapter. Hono runs on these runtimes: - Cloudflare Workers (`workerd`) - Deno - Bun - Fastly Compute - AWS Lambda - Node.js - Vercel (edge-light) - WebAssembly (w/ [WebAssembly System Interface (WASI)][wasi] via [`wasi:http`][wasi-http]) It also works on Netlify and other platforms. The same code runs on all platforms. Cloudflare Workers, Deno, Shopify, and others launched [WinterCG](https://wintercg.org) to discuss the possibility of using the Web Standards to enable "web-interoperability". Hono will follow their steps and go for **the Standard of the Web Standards**. [wasi]: https://github.com/WebAssembly/wasi [wasi-http]: https://github.com/WebAssembly/wasi-http # Hono Stacks Hono makes easy things easy and hard things easy. It is suitable for not just only returning JSON, but it's also great for building the full-stack application including REST API servers and the client. ## RPC Hono's RPC feature allows you to share API specs with little change to your code. The client generated by `hc` will read the spec and access the endpoint type-safety. The following libraries make it possible. - Hono - API Server - [Zod](https://zod.dev) - Validator - [Zod Validator Middleware](https://github.com/honojs/middleware/tree/main/packages/zod-validator) - `hc` - HTTP Client We can call the set of these components the **Hono Stack**. Now let's create an API server and a client with it. ## Writing API First, write an endpoint that receives a GET request and returns JSON. ```ts twoslash import { Hono } from 'hono' const app = new Hono() app.get('/hello', (c) => { return c.json({ message: `Hello!`, }) }) ``` ## Validation with Zod Validate with Zod to receive the value of the query parameter. ![](/images/sc01.gif) ```ts import { zValidator } from '@hono/zod-validator' import * as z from 'zod' app.get( '/hello', zValidator( 'query', z.object({ name: z.string(), }) ), (c) => { const { name } = c.req.valid('query') return c.json({ message: `Hello! ${name}`, }) } ) ``` ## Sharing the Types To emit an endpoint specification, export its type. ::: warning For the RPC to infer routes correctly, all included methods must be chained, and the endpoint or app type must be inferred from a declared variable. For more, see [Best Practices for RPC](https://hono.dev/docs/guides/best-practices#if-you-want-to-use-rpc-features). ::: ```ts{1,17} const route = app.get( '/hello', zValidator( 'query', z.object({ name: z.string(), }) ), (c) => { const { name } = c.req.valid('query') return c.json({ message: `Hello! ${name}`, }) } ) export type AppType = typeof route ``` ## Client Next, The client-side implementation. Create a client object by passing the `AppType` type to `hc` as generics. Then, magically, completion works and the endpoint path and request type are suggested. ![](/images/sc03.gif) ```ts import { AppType } from './server' import { hc } from 'hono/client' const client = hc('/api') const res = await client.hello.$get({ query: { name: 'Hono', }, }) ``` The `Response` is compatible with the fetch API, but the data that can be retrieved with `json()` has a type. ![](/images/sc04.gif) ```ts const data = await res.json() console.log(`${data.message}`) ``` Sharing API specifications means that you can be aware of server-side changes. ![](/images/ss03.png) ## With React You can create applications on Cloudflare Pages using React. The API server. ```ts // functions/api/[[route]].ts import { Hono } from 'hono' import { handle } from 'hono/cloudflare-pages' import * as z from 'zod' import { zValidator } from '@hono/zod-validator' const app = new Hono() const schema = z.object({ id: z.string(), title: z.string(), }) type Todo = z.infer const todos: Todo[] = [] const route = app .post('/todo', zValidator('form', schema), (c) => { const todo = c.req.valid('form') todos.push(todo) return c.json({ message: 'created!', }) }) .get((c) => { return c.json({ todos, }) }) export type AppType = typeof route export const onRequest = handle(app, '/api') ``` The client with React and React Query. ```tsx // src/App.tsx import { useQuery, useMutation, QueryClient, QueryClientProvider, } from '@tanstack/react-query' import { AppType } from '../functions/api/[[route]]' import { hc, InferResponseType, InferRequestType } from 'hono/client' const queryClient = new QueryClient() const client = hc('/api') export default function App() { return ( ) } const Todos = () => { const query = useQuery({ queryKey: ['todos'], queryFn: async () => { const res = await client.todo.$get() return await res.json() }, }) const $post = client.todo.$post const mutation = useMutation< InferResponseType, Error, InferRequestType['form'] >({ mutationFn: async (todo) => { const res = await $post({ form: todo, }) return await res.json() }, onSuccess: async () => { queryClient.invalidateQueries({ queryKey: ['todos'] }) }, onError: (error) => { console.log(error) }, }) return (
    {query.data?.todos.map((todo) => (
  • {todo.title}
  • ))}
) } ``` # Routers The routers are the most important features for Hono. Hono has five routers. ## RegExpRouter **RegExpRouter** is the fastest router in the JavaScript world. Although this is called "RegExp", it is not an Express-like implementation using [path-to-regexp](https://github.com/pillarjs/path-to-regexp). They are using linear loops. Therefore, regular expression matching will be performed for all routes and the performance will be degraded as you have more routes. ![](/images/router-linear.jpg) Hono's RegExpRouter turns the route pattern into "one large regular expression". Then it can get the result with one-time matching. ![](/images/router-regexp.jpg) This works faster than methods that use tree-based algorithms such as radix-tree in most cases. However, RegExpRouter doesn't support all routing patterns, so it's usually used in combination with one of the other routers below that support all routing patterns. ## TrieRouter **TrieRouter** is the router using the Trie-tree algorithm. Like RegExpRouter, it does not use linear loops. ![](/images/router-tree.jpg) This router is not as fast as the RegExpRouter, but it is much faster than the Express router. TrieRouter supports all patterns. ## SmartRouter **SmartRouter** is useful when you're using multiple routers. It selects the best router by inferring from the registered routers. Hono uses SmartRouter, RegExpRouter, and TrieRouter by default: ```ts // Inside the core of Hono. readonly defaultRouter: Router = new SmartRouter({ routers: [new RegExpRouter(), new TrieRouter()], }) ``` When the application starts, SmartRouter detects the fastest router based on routing and continues to use it. ## LinearRouter RegExpRouter is fast, but the route registration phase can be slightly slow. So, it's not suitable for an environment that initializes with every request. **LinearRouter** is optimized for "one shot" situations. Route registration is significantly faster than RegExpRouter because it adds the route without compiling strings, using a linear approach. The following is one of the benchmark results, which includes the route registration phase. ```console • GET /user/lookup/username/hey ----------------------------------------------------- ----------------------------- LinearRouter 1.82 µs/iter (1.7 µs … 2.04 µs) 1.84 µs 2.04 µs 2.04 µs MedleyRouter 4.44 µs/iter (4.34 µs … 4.54 µs) 4.48 µs 4.54 µs 4.54 µs FindMyWay 60.36 µs/iter (45.5 µs … 1.9 ms) 59.88 µs 78.13 µs 82.92 µs KoaTreeRouter 3.81 µs/iter (3.73 µs … 3.87 µs) 3.84 µs 3.87 µs 3.87 µs TrekRouter 5.84 µs/iter (5.75 µs … 6.04 µs) 5.86 µs 6.04 µs 6.04 µs summary for GET /user/lookup/username/hey LinearRouter 2.1x faster than KoaTreeRouter 2.45x faster than MedleyRouter 3.21x faster than TrekRouter 33.24x faster than FindMyWay ``` ## PatternRouter **PatternRouter** is the smallest router among Hono's routers. While Hono is already compact, if you need to make it even smaller for an environment with limited resources, use PatternRouter. An application using only PatternRouter is under 15KB in size. ```console $ npx wrangler deploy --minify ./src/index.ts ⛅️ wrangler 3.20.0 ------------------- Total Upload: 14.68 KiB / gzip: 5.38 KiB ``` # Developer Experience To create a great application, we need great development experience. Fortunately, we can write applications for Cloudflare Workers, Deno, and Bun in TypeScript without having the need to transpile it to JavaScript. Hono is written in TypeScript and can make applications type-safe. # Philosophy In this section, we talk about the concept, or philosophy, of Hono. ## Motivation At first, I just wanted to create a web application on Cloudflare Workers. But, there was no good framework that works on Cloudflare Workers. So, I started building Hono. I thought it would be a good opportunity to learn how to build a router using Trie trees. Then a friend showed up with ultra crazy fast router called "RegExpRouter". And I also have a friend who created the Basic authentication middleware. Using only Web Standard APIs, we could make it work on Deno and Bun. When people asked "is there Express for Bun?", we could answer, "no, but there is Hono". (Although Express works on Bun now.) We also have friends who make GraphQL servers, Firebase authentication, and Sentry middleware. And, we also have a Node.js adapter. An ecosystem has sprung up. In other words, Hono is damn fast, makes a lot of things possible, and works anywhere. We might imagine that Hono could become the **Standard for Web Standards**. # Benchmarks Benchmarks are only benchmarks, but they are important to us. ## Routers We measured the speeds of a bunch of JavaScript routers. For example, `find-my-way` is a very fast router used inside Fastify. - @medley/router - find-my-way - koa-tree-router - trek-router - express (includes handling) - koa-router First, we registered the following routing to each of our routers. These are similar to those used in the real world. ```ts twoslash interface Route { method: string path: string } // ---cut--- export const routes: Route[] = [ { method: 'GET', path: '/user' }, { method: 'GET', path: '/user/comments' }, { method: 'GET', path: '/user/avatar' }, { method: 'GET', path: '/user/lookup/username/:username' }, { method: 'GET', path: '/user/lookup/email/:address' }, { method: 'GET', path: '/event/:id' }, { method: 'GET', path: '/event/:id/comments' }, { method: 'POST', path: '/event/:id/comment' }, { method: 'GET', path: '/map/:location/events' }, { method: 'GET', path: '/status' }, { method: 'GET', path: '/very/deeply/nested/route/hello/there' }, { method: 'GET', path: '/static/*' }, ] ``` Then we sent the Request to the endpoints like below. ```ts twoslash interface Route { method: string path: string } // ---cut--- const routes: (Route & { name: string })[] = [ { name: 'short static', method: 'GET', path: '/user', }, { name: 'static with same radix', method: 'GET', path: '/user/comments', }, { name: 'dynamic route', method: 'GET', path: '/user/lookup/username/hey', }, { name: 'mixed static dynamic', method: 'GET', path: '/event/abcd1234/comments', }, { name: 'post', method: 'POST', path: '/event/abcd1234/comment', }, { name: 'long static', method: 'GET', path: '/very/deeply/nested/route/hello/there', }, { name: 'wildcard', method: 'GET', path: '/static/index.html', }, ] ``` Let's see the results. ### On Node.js The following screenshots show the results on Node.js. ![](/images/bench01.png) ![](/images/bench02.png) ![](/images/bench03.png) ![](/images/bench04.png) ![](/images/bench05.png) ![](/images/bench06.png) ![](/images/bench07.png) ![](/images/bench08.png) ### On Bun The following screenshots show the results on Bun. ![](/images/bench09.png) ![](/images/bench10.png) ![](/images/bench11.png) ![](/images/bench12.png) ![](/images/bench13.png) ![](/images/bench14.png) ![](/images/bench15.png) ![](/images/bench16.png) ## Cloudflare Workers **Hono is the fastest**, compared to other routers for Cloudflare Workers. - Machine: Apple MacBook Pro, 32 GiB, M1 Pro - Scripts: [benchmarks/handle-event](https://github.com/honojs/hono/tree/main/benchmarks/handle-event) ``` 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. ``` ## Deno **Hono is the fastest**, compared to other frameworks for Deno. - Machine: Apple MacBook Pro, 32 GiB, M1 Pro, Deno v1.22.0 - Scripts: [benchmarks/deno](https://github.com/honojs/hono/tree/main/benchmarks/deno) - Method: `bombardier --fasthttp -d 10s -c 100 'http://localhost:8000/user/lookup/username/foo'` | Framework | Version | Results | | --------- | :----------: | -----------------------: | | **Hono** | 3.0.0 | **Requests/sec: 136112** | | Fast | 4.0.0-beta.1 | Requests/sec: 103214 | | Megalo | 0.3.0 | Requests/sec: 64597 | | Faster | 5.7 | Requests/sec: 54801 | | oak | 10.5.1 | Requests/sec: 43326 | | opine | 2.2.0 | Requests/sec: 30700 | Another benchmark result: [denosaurs/bench](https://github.com/denosaurs/bench) ## Bun Hono is one of the fastest frameworks for Bun. You can see it below. - [SaltyAom/bun-http-framework-benchmark](https://github.com/SaltyAom/bun-http-framework-benchmark) # 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) - [Casbin](https://github.com/honojs/middleware/tree/main/packages/casbin) - [Clerk Auth](https://github.com/honojs/middleware/tree/main/packages/clerk-auth) - [Cloudflare Access](https://github.com/honojs/middleware/tree/main/packages/cloudflare-access) - [OAuth Providers](https://github.com/honojs/middleware/tree/main/packages/oauth-providers) - [OIDC Auth](https://github.com/honojs/middleware/tree/main/packages/oidc-auth) - [Firebase Auth](https://github.com/honojs/middleware/tree/main/packages/firebase-auth) - [Verify RSA JWT (JWKS)](https://github.com/wataruoguchi/verify-rsa-jwt-cloudflare-worker) - [Stytch Auth](https://github.com/honojs/middleware/tree/main/packages/stytch-auth) ### Validators - [Ajv Validator](https://github.com/honojs/middleware/tree/main/packages/ajv-validator) - [ArkType Validator](https://github.com/honojs/middleware/tree/main/packages/arktype-validator) - [Class Validator](https://github.com/honojs/middleware/tree/main/packages/class-validator) - [Conform Validator](https://github.com/honojs/middleware/tree/main/packages/conform-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](https://github.com/scalar/scalar/tree/main/integrations/hono) - [Swagger UI](https://github.com/honojs/middleware/tree/main/packages/swagger-ui) - [Swagger Editor](https://github.com/honojs/middleware/tree/main/packages/swagger-editor) - [Hono OpenAPI](https://github.com/rhinobase/hono-openapi) - [hono-zod-openapi](https://github.com/paolostyle/hono-zod-openapi) ### Development - [ESLint Config](https://github.com/honojs/middleware/tree/main/packages/eslint-config) - [SSG Plugin Essential](https://github.com/honojs/middleware/tree/main/packages/ssg-plugins-essential) ### Monitoring / Tracing - [Apitally (API monitoring & analytics)](https://docs.apitally.io/frameworks/hono) - [Highlight.io](https://www.highlight.io/docs/getting-started/backend-sdk/js/hono) - [LogTape (Logging)](https://logtape.org/manual/integrations#hono) - [OpenTelemetry](https://github.com/honojs/middleware/tree/main/packages/otel) - [Prometheus Metrics](https://github.com/honojs/middleware/tree/main/packages/prometheus) - [Sentry](https://github.com/honojs/middleware/tree/main/packages/sentry) - [Pino logger](https://github.com/maou-shonen/hono-pino) ### Server / Adapter - [GraphQL Server](https://github.com/honojs/middleware/tree/main/packages/graphql-server) - [Node WebSocket Helper](https://github.com/honojs/middleware/tree/main/packages/node-ws) - [tRPC Server](https://github.com/honojs/middleware/tree/main/packages/trpc-server) ### Transpiler - [Bun Transpiler](https://github.com/honojs/middleware/tree/main/packages/bun-transpiler) - [esbuild Transpiler](https://github.com/honojs/middleware/tree/main/packages/esbuild-transpiler) ### UI / Renderer - [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) ### Queue / Job Processing - [GlideMQ (Message Queue REST API + SSE)](https://github.com/avifenesh/glidemq-hono) ### Internationalization - [Intlayer i18n](https://intlayer.org/doc/environment/hono) ### Utilities - [Bun Compress](https://github.com/honojs/middleware/tree/main/packages/bun-compress) - [Cap Checkpoint](https://capjs.js.org/guide/middleware/hono.html) - [Event Emitter](https://github.com/honojs/middleware/tree/main/packages/event-emitter) - [Geo](https://github.com/ktkongtong/hono-geo-middleware/tree/main/packages/middleware) - [Hono Rate Limiter](https://github.com/rhinobase/hono-rate-limiter) - [Hono Problem Details (RFC 9457)](https://github.com/paveg/hono-problem-details) - [Hono Simple DI](https://github.com/maou-shonen/hono-simple-DI) - [Idempotency (Stripe-style idempotency keys)](https://github.com/paveg/hono-idempotency) - [jsonv-ts (Validator, OpenAPI, MCP)](https://github.com/dswbx/jsonv-ts) - [MCP](https://github.com/honojs/middleware/tree/main/packages/mcp) - [RONIN (Database)](https://github.com/ronin-co/hono-client) - [Session](https://github.com/honojs/middleware/tree/main/packages/session) - [tsyringe](https://github.com/honojs/middleware/tree/main/packages/tsyringe) - [User Agent based Blocker](https://github.com/honojs/middleware/tree/main/packages/ua-blocker) # CSRF Protection This middleware protects against CSRF attacks by checking both the `Origin` header and the `Sec-Fetch-Site` header. The request is allowed if either validation passes. The middleware only validates requests that: - Use unsafe HTTP methods (not GET, HEAD, or OPTIONS) - Have content types that can be sent by HTML forms (`application/x-www-form-urlencoded`, `multipart/form-data`, or `text/plain`) Old browsers that do not send `Origin` headers, or environments that use reverse proxies to remove these headers, may not work well. In such environments, use other CSRF token methods. ## Import ```ts import { Hono } from 'hono' import { csrf } from 'hono/csrf' ``` ## Usage ```ts const app = new Hono() // Default: both origin and sec-fetch-site validation app.use(csrf()) // Allow specific origins app.use(csrf({ origin: 'https://myapp.example.com' })) // Allow multiple origins app.use( csrf({ origin: [ 'https://myapp.example.com', 'https://development.myapp.example.com', ], }) ) // Allow specific sec-fetch-site values app.use(csrf({ secFetchSite: 'same-origin' })) app.use(csrf({ secFetchSite: ['same-origin', 'none'] })) // Dynamic origin validation // 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), }) ) // Dynamic sec-fetch-site validation app.use( csrf({ secFetchSite: (secFetchSite, c) => { // Always allow same-origin if (secFetchSite === 'same-origin') return true // Allow cross-site for webhook endpoints if ( secFetchSite === 'cross-site' && c.req.path.startsWith('/webhook/') ) { return true } return false }, }) ) ``` ## Options ### origin: `string` | `string[]` | `Function` Specify allowed origins for CSRF protection. - **`string`**: Single allowed origin (e.g., `'https://example.com'`) - **`string[]`**: Array of allowed origins - **`Function`**: Custom handler `(origin: string, context: Context) => boolean` for flexible origin validation and bypass logic **Default**: Only same origin as the request URL The function handler receives the request's `Origin` header value and the request context, allowing for dynamic validation based on request properties like path, headers, or other context data. ### secFetchSite: `string` | `string[]` | `Function` Specify allowed Sec-Fetch-Site header values for CSRF protection using [Fetch Metadata](https://web.dev/articles/fetch-metadata). - **`string`**: Single allowed value (e.g., `'same-origin'`) - **`string[]`**: Array of allowed values (e.g., `['same-origin', 'none']`) - **`Function`**: Custom handler `(secFetchSite: string, context: Context) => boolean` for flexible validation **Default**: Only allows `'same-origin'` Standard Sec-Fetch-Site values: - `same-origin`: Request from same origin - `same-site`: Request from same site (different subdomain) - `cross-site`: Request from different site - `none`: Request not from a web page (e.g., browser address bar, bookmark) The function handler receives the request's `Sec-Fetch-Site` header value and the request context, enabling dynamic validation based on request properties. # 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. # 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 | | trustedTypes | [Trusted Types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting | | requireTrustedTypesFor | [Require Trusted Types For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/require-trusted-types-for) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting | | crossOriginEmbedderPolicy | [Cross-Origin-Embedder-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy) | require-corp | **False** | | crossOriginResourcePolicy | [Cross-Origin-Resource-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy) | same-origin | True | | crossOriginOpenerPolicy | [Cross-Origin-Opener-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy) | same-origin | True | | originAgentCluster | [Origin-Agent-Cluster](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin-Agent-Cluster) | ?1 | True | | referrerPolicy | [Referrer-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) | no-referrer | True | | reportingEndpoints | [Reporting-Endpoints](https://www.w3.org/TR/reporting-1/#header) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting | | reportTo | [Report-To](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting | | strictTransportSecurity | [Strict-Transport-Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) | max-age=15552000; includeSubDomains | True | | xContentTypeOptions | [X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options) | nosniff | True | | xDnsPrefetchControl | [X-DNS-Prefetch-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control) | off | True | | xDownloadOptions | [X-Download-Options](https://learn.microsoft.com/en-us/archive/blogs/ie/ie8-security-part-v-comprehensive-protection#mime-handling-force-save) | noopen | True | | xFrameOptions | [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) | SAMEORIGIN | True | | xPermittedCrossDomainPolicies | [X-Permitted-Cross-Domain-Policies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Permitted-Cross-Domain-Policies) | none | True | | xXssProtection | [X-XSS-Protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection) | 0 | True | | permissionPolicy | [Permissions-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy) | Usage: [Setting Permission-Policy](#setting-permission-policy) | No Setting | ## Middleware Conflict Please be cautious about the order of specification when dealing with middleware that manipulates the same header. In this case, Secure-headers operates and the `x-powered-by` is removed: ```ts const app = new Hono() app.use(secureHeaders()) app.use(poweredBy()) ``` In this case, Powered-By operates and the `x-powered-by` is added: ```ts const app = new Hono() app.use(poweredBy()) app.use(secureHeaders()) ``` ## Setting Content-Security-Policy ```ts const app = new Hono() app.use( '/test', secureHeaders({ reportingEndpoints: [ { name: 'endpoint-1', url: 'https://example.com/reports', }, ], // -- or alternatively // reportTo: [ // { // group: 'endpoint-1', // max_age: 10886400, // endpoints: [{ url: 'https://example.com/reports' }], // }, // ], contentSecurityPolicy: { defaultSrc: ["'self'"], baseUri: ["'self'"], childSrc: ["'self'"], connectSrc: ["'self'"], fontSrc: ["'self'", 'https:', 'data:'], formAction: ["'self'"], frameAncestors: ["'self'"], frameSrc: ["'self'"], imgSrc: ["'self'", 'data:'], manifestSrc: ["'self'"], mediaSrc: ["'self'"], objectSrc: ["'none'"], reportTo: 'endpoint-1', reportUri: '/csp-report', 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 */} ``` `main.ts` is a script to register the Service Worker: ```ts function register() { navigator.serviceWorker .register('/sw.ts', { scope: '/sw', type: 'module' }) .then( function (_registration) { console.log('Register Service Worker: Success') }, function (_error) { console.log('Register Service Worker: Error') } ) } function start() { navigator.serviceWorker .getRegistrations() .then(function (registrations) { for (const registration of registrations) { console.log('Unregister Service Worker') registration.unregister() } register() }) } start() ``` In `sw.ts`, create an application using Hono and register it to the `fetch` event with the Service Worker adapter’s `handle` function. This allows the Hono application to intercept access to `/sw`. ```ts // To support types // https://github.com/microsoft/TypeScript/issues/14877 declare const self: ServiceWorkerGlobalScope import { Hono } from 'hono' import { handle } from 'hono/service-worker' const app = new Hono().basePath('/sw') app.get('/', (c) => c.text('Hello World')) self.addEventListener('fetch', handle(app)) ``` ### Using `fire()` The `fire()` function automatically calls `addEventListener('fetch', handle(app))` for you, making the code more concise. ```ts import { Hono } from 'hono' import { fire } from 'hono/service-worker' const app = new Hono().basePath('/sw') app.get('/', (c) => c.text('Hello World')) fire(app) ``` ## 3. Run Start the development server. ::: code-group ```sh [npm] npm run dev ``` ```sh [yarn] yarn dev ``` ```sh [pnpm] pnpm run dev ``` ```sh [bun] bun run dev ``` ::: By default, the development server will run on port `5173`. Access `http://localhost:5173/` in your browser to complete the Service Worker registration. Then, access `/sw` to see the response from the Hono application. # Deno [Deno](https://deno.com/) is a JavaScript runtime built on V8. It's not Node.js. Hono also works on Deno. You can use Hono, write the code with TypeScript, run the application with the `deno` command, and deploy it to "Deno Deploy". ## 1. Install Deno First, install `deno` command. Please refer to [the official document](https://docs.deno.com/runtime/getting_started/installation/). ## 2. Setup A starter for Deno is available. Start your project with the [`deno init`](https://docs.deno.com/runtime/reference/cli/init/) command. ```sh deno init --npm hono --template=deno my-app ``` Move into `my-app`. For Deno, you don't have to install Hono explicitly. ```sh cd my-app ``` ## 3. Hello World Edit `main.ts`: ```ts [main.ts] import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Hello Deno!')) Deno.serve(app.fetch) ``` ## 4. Run Run the development server locally. Then, access `http://localhost:8000` in your Web browser. ```sh deno task start ``` ## Change port number You can specify the port number by updating the arguments of `Deno.serve` in `main.ts`: ```ts Deno.serve(app.fetch) // [!code --] Deno.serve({ port: 8787 }, app.fetch) // [!code ++] ``` ## Serve static files To serve static files, use `serveStatic` imported from `hono/deno`. ```ts import { Hono } from 'hono' import { serveStatic } from 'hono/deno' const app = new Hono() app.use('/static/*', serveStatic({ root: './' })) app.use('/favicon.ico', serveStatic({ path: './favicon.ico' })) app.get('/', (c) => c.text('You can access: /static/hello.txt')) app.get('*', serveStatic({ path: './static/fallback.txt' })) Deno.serve(app.fetch) ``` For the above code, it will work well with the following directory structure. ``` ./ ├── favicon.ico ├── index.ts └── static ├── demo │ └── index.html ├── fallback.txt ├── hello.txt └── images └── dinotocat.png ``` ### `rewriteRequestPath` If you want to map `http://localhost:8000/static/*` to `./statics`, you can use the `rewriteRequestPath` option: ```ts app.get( '/static/*', serveStatic({ root: './', rewriteRequestPath: (path) => path.replace(/^\/static/, '/statics'), }) ) ``` ### `mimes` You can add MIME types with `mimes`: ```ts app.get( '/static/*', serveStatic({ mimes: { m3u8: 'application/vnd.apple.mpegurl', ts: 'video/mp2t', }, }) ) ``` ### `onFound` You can specify handling when the requested file is found with `onFound`: ```ts app.get( '/static/*', serveStatic({ // ... onFound: (_path, c) => { c.header('Cache-Control', `public, immutable, max-age=31536000`) }, }) ) ``` ### `onNotFound` You can specify handling when the requested file is not found with `onNotFound`: ```ts app.get( '/static/*', serveStatic({ onNotFound: (path, c) => { console.log(`${path} is not found, you access ${c.req.path}`) }, }) ) ``` ### `precompressed` The `precompressed` option checks if files with extensions like `.br` or `.gz` are available and serves them based on the `Accept-Encoding` header. It prioritizes Brotli, then Zstd, and Gzip. If none are available, it serves the original file. ```ts app.get( '/static/*', serveStatic({ precompressed: true, }) ) ``` ## Deno Deploy Deno Deploy is a serverless platform for running JavaScript and TypeScript applications in the cloud. It provides a management plane for deploying and running applications through integrations like GitHub deployment. Hono also works on Deno Deploy. Please refer to [the official document](https://docs.deno.com/deploy/manual/). ## Testing Testing the application on Deno is easy. You can write with `Deno.test` and use `assert` or `assertEquals` from [@std/assert](https://jsr.io/@std/assert). ```sh deno add jsr:@std/assert ``` ```ts [hello.ts] import { Hono } from 'hono' import { assertEquals } from '@std/assert' Deno.test('Hello World', async () => { const app = new Hono() app.get('/', (c) => c.text('Please test me')) const res = await app.request('http://localhost/') assertEquals(res.status, 200) }) ``` Then run the command: ```sh deno test hello.ts ``` ## npm and JSR Hono is available on both [npm](https://www.npmjs.com/package/hono) and [JSR](https://jsr.io/@hono/hono) (the JavaScript Registry). You can use either `npm:hono` or `jsr:@hono/hono` in your `deno.json`: ```json { "imports": { "hono": "jsr:@hono/hono" // [!code --] "hono": "npm:hono" // [!code ++] } } ``` To use middleware you need to use the [Deno directory](https://docs.deno.com/runtime/fundamentals/configuration/#custom-path-mappings) syntax in the import. ```json { "imports": { "hono/": "npm:/hono/" } } ``` When using third-party middleware, you may need to use Hono from the same registry as the middleware for proper TypeScript type inference. For example, if using the middleware from npm, you should also use Hono from npm: ```json { "imports": { "hono": "npm:hono", "zod": "npm:zod", "@hono/zod-validator": "npm:@hono/zod-validator" } } ``` We also provide many third-party middleware packages on [JSR](https://jsr.io/@hono). When using the middleware on JSR, use Hono from JSR: ```json { "imports": { "hono": "jsr:@hono/hono", "zod": "npm:zod", "@hono/zod-validator": "jsr:@hono/zod-validator" } } ``` # Lambda@Edge [Lambda@Edge](https://aws.amazon.com/lambda/edge/) is a serverless platform by Amazon Web Services. It allows you to run Lambda functions at Amazon CloudFront's edge locations, enabling you to customize behaviors for HTTP requests/responses. Hono supports Lambda@Edge with the Node.js 18+ environment. ## 1. Setup When creating the application on Lambda@Edge, [CDK](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-cdk.html) is useful to set up the functions such as CloudFront, IAM Role, API Gateway, and others. Initialize your project with the `cdk` CLI. ::: code-group ```sh [npm] mkdir my-app cd my-app cdk init app -l typescript npm i hono mkdir lambda ``` ```sh [yarn] mkdir my-app cd my-app cdk init app -l typescript yarn add hono mkdir lambda ``` ```sh [pnpm] mkdir my-app cd my-app cdk init app -l typescript pnpm add hono mkdir lambda ``` ```sh [bun] mkdir my-app cd my-app cdk init app -l typescript bun add hono mkdir lambda ``` ::: ## 2. Hello World Edit `lambda/index_edge.ts`. ```ts import { Hono } from 'hono' import { handle } from 'hono/lambda-edge' const app = new Hono() app.get('/', (c) => c.text('Hello Hono on Lambda@Edge!')) export const handler = handle(app) ``` ## 3. Deploy Edit `bin/my-app.ts`. ```ts #!/usr/bin/env node import 'source-map-support/register' import * as cdk from 'aws-cdk-lib' import { MyAppStack } from '../lib/my-app-stack' const app = new cdk.App() new MyAppStack(app, 'MyAppStack', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: 'us-east-1', }, }) ``` Edit `lambda/cdk-stack.ts`. ```ts import { Construct } from 'constructs' import * as cdk from 'aws-cdk-lib' import * as cloudfront from 'aws-cdk-lib/aws-cloudfront' import * as origins from 'aws-cdk-lib/aws-cloudfront-origins' import * as lambda from 'aws-cdk-lib/aws-lambda' import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' import * as s3 from 'aws-cdk-lib/aws-s3' export class MyAppStack extends cdk.Stack { public readonly edgeFn: lambda.Function constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props) const edgeFn = new NodejsFunction(this, 'edgeViewer', { entry: 'lambda/index_edge.ts', handler: 'handler', runtime: lambda.Runtime.NODEJS_20_X, }) // Upload any html const originBucket = new s3.Bucket(this, 'originBucket') new cloudfront.Distribution(this, 'Cdn', { defaultBehavior: { origin: new origins.S3Origin(originBucket), edgeLambdas: [ { functionVersion: edgeFn.currentVersion, eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST, }, ], }, }) } } ``` Finally, run the command to deploy: ```sh cdk deploy ``` ## Callback If you want to add Basic Auth and continue with request processing after verification, you can use `c.env.callback()` ```ts import { Hono } from 'hono' import { basicAuth } from 'hono/basic-auth' import type { Callback, CloudFrontRequest } from 'hono/lambda-edge' import { handle } from 'hono/lambda-edge' type Bindings = { callback: Callback request: CloudFrontRequest } const app = new Hono<{ Bindings: Bindings }>() app.get( '*', basicAuth({ username: 'hono', password: 'acoolproject', }) ) app.get('/', async (c, next) => { await next() c.env.callback(null, c.env.request) }) export const handler = handle(app) ``` # Alibaba Cloud Function Compute [Alibaba Cloud Function Compute](https://www.alibabacloud.com/en/product/function-compute) is a fully managed, event-driven compute service. Function Compute allows you to focus on writing and uploading code without having to manage infrastructure such as servers. This guide uses a third-party adapter [rwv/hono-alibaba-cloud-fc3-adapter](https://github.com/rwv/hono-alibaba-cloud-fc3-adapter) to run Hono on Alibaba Cloud Function Compute. ## 1. Setup ::: code-group ```sh [npm] mkdir my-app cd my-app npm i hono hono-alibaba-cloud-fc3-adapter npm i -D @serverless-devs/s esbuild mkdir src touch src/index.ts ``` ```sh [yarn] mkdir my-app cd my-app yarn add hono hono-alibaba-cloud-fc3-adapter yarn add -D @serverless-devs/s esbuild mkdir src touch src/index.ts ``` ```sh [pnpm] mkdir my-app cd my-app pnpm add hono hono-alibaba-cloud-fc3-adapter pnpm add -D @serverless-devs/s esbuild mkdir src touch src/index.ts ``` ```sh [bun] mkdir my-app cd my-app bun add hono hono-alibaba-cloud-fc3-adapter bun add -D esbuild @serverless-devs/s mkdir src touch src/index.ts ``` ::: ## 2. Hello World Edit `src/index.ts`. ```ts import { Hono } from 'hono' import { handle } from 'hono-alibaba-cloud-fc3-adapter' const app = new Hono() app.get('/', (c) => c.text('Hello Hono!')) export const handler = handle(app) ``` ## 3. Setup serverless-devs > [serverless-devs](https://github.com/Serverless-Devs/Serverless-Devs) is an open source and open serverless developer platform dedicated to providing developers with a powerful tool chain system. Through this platform, developers can not only experience multi cloud serverless products with one click and rapidly deploy serverless projects, but also manage projects in the whole life cycle of serverless applications, and combine serverless devs with other tools / platforms very simply and quickly to further improve the efficiency of R & D, operation and maintenance. Add the Alibaba Cloud AccessKeyID & AccessKeySecret ```sh npx s config add # Please select a provider: Alibaba Cloud (alibaba) # Input your AccessKeyID & AccessKeySecret ``` Edit `s.yaml` ```yaml edition: 3.0.0 name: my-app access: 'default' vars: region: 'us-west-1' resources: my-app: component: fc3 props: region: ${vars.region} functionName: 'my-app' description: 'Hello World by Hono' runtime: 'nodejs20' code: ./dist handler: index.handler memorySize: 1024 timeout: 300 ``` Edit `scripts` section in `package.json`: ```json { "scripts": { "build": "esbuild --bundle --outfile=./dist/index.js --platform=node --target=node20 ./src/index.ts", "deploy": "s deploy -y" } } ``` ## 4. Deploy Finally, run the command to deploy: ```sh npm run build # Compile the TypeScript code to JavaScript npm run deploy # Deploy the function to Alibaba Cloud Function Compute ``` # Google Cloud Run [Google Cloud Run](https://cloud.google.com/run) is a serverless platform built by Google Cloud. You can run your code in response to events and Google automatically manages the underlying compute resources for you. Google Cloud Run uses containers to run your service. This means you can use any runtime you like (E.g., Deno or Bun) by providing a Dockerfile. If no Dockerfile is provided Google Cloud Run will use the default Node.js buildpack. This guide assumes you already have a Google Cloud account and a billing account. ## 1. Install the CLI When working with Google Cloud Platform, it is easiest to work with the [gcloud CLI](https://cloud.google.com/sdk/docs/install). For example, on MacOS using Homebrew: ```sh brew install --cask gcloud-cli ``` Authenticate with the CLI. ```sh gcloud auth login ``` ## 2. Project setup Create a project. Accept the auto-generated project ID at the prompt. ```sh gcloud projects create --set-as-default --name="my app" ``` Create environment variables for your project ID and project number for easy reuse. It may take ~30 seconds before the project successfully returns with the `gcloud projects list` command. ```sh PROJECT_ID=$(gcloud projects list \ --format='value(projectId)' \ --filter='name="my app"') PROJECT_NUMBER=$(gcloud projects list \ --format='value(projectNumber)' \ --filter='name="my app"') echo $PROJECT_ID $PROJECT_NUMBER ``` Find your billing account ID. ```sh gcloud billing accounts list ``` Add your billing account from the prior command to the project. ```sh gcloud billing projects link $PROJECT_ID \ --billing-account=[billing_account_id] ``` Enable the required APIs. ```sh gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com ``` Update the service account permissions to have access to Cloud Build. ```sh gcloud projects add-iam-policy-binding $PROJECT_ID \ --member=serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \ --role=roles/run.builder ``` ## 3. Hello World Start your project with "create-hono" command. Select `nodejs`. ```sh npm create hono@latest my-app ``` Move to `my-app` and install the dependencies. ```sh cd my-app npm i ``` Update the port in `src/index.ts` to be `8080`. ```ts import { serve } from '@hono/node-server' import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => { return c.text('Hello Hono!') }) serve({ fetch: app.fetch, port: 3000 // [!code --] port: 8080 // [!code ++] }, (info) => { console.log(`Server is running on http://localhost:${info.port}`) }) ``` Run the development server locally. Then, access http://localhost:8080 in your Web browser. ```sh npm run dev ``` ## 4. Deploy Start the deployment and follow the interactive prompts (E.g., select a region). ```sh gcloud run deploy my-app --source . --allow-unauthenticated ``` ## Changing runtimes If you want to deploy using Deno or Bun runtimes (or a customised Nodejs container), add a `Dockerfile` (and optionally `.dockerignore`) with your desired environment. For information on containerizing, please refer to: - [Node.js](/docs/getting-started/nodejs#building-deployment) - [Bun](https://bun.com/guides/ecosystem/docker) - [Deno](https://docs.deno.com/examples/google_cloud_run_tutorial) # Node.js [Node.js](https://nodejs.org/) is an open-source, cross-platform JavaScript runtime environment. Hono was not designed for Node.js at first, but with a [Node.js Adapter](https://github.com/honojs/node-server), it can run on Node.js as well. ::: info It works on Node.js versions greater than 18.x. The specific required Node.js versions are as follows: - 18.x => 18.14.1+ - 19.x => 19.7.0+ - 20.x => 20.0.0+ Essentially, you can simply use the latest version of each major release. ::: ## 1. Setup A starter for Node.js is available. Start your project with "create-hono" command. Select `nodejs` template for this example. ::: code-group ```sh [npm] npm create hono@latest my-app ``` ```sh [yarn] yarn create hono my-app ``` ```sh [pnpm] pnpm create hono my-app ``` ```sh [bun] bun create hono@latest my-app ``` ```sh [deno] deno init --npm hono my-app ``` ::: Move to `my-app` and install the dependencies. ::: code-group ```sh [npm] cd my-app npm i ``` ```sh [yarn] cd my-app yarn ``` ```sh [pnpm] cd my-app pnpm i ``` ```sh [bun] cd my-app bun i ``` ::: ## 2. Hello World Edit `src/index.ts`: ```ts import { serve } from '@hono/node-server' import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Hello Node.js!')) serve(app) ``` If you want to gracefully shut down the server, write it like this: ```ts const server = serve(app) // graceful shutdown process.on('SIGINT', () => { server.close() process.exit(0) }) process.on('SIGTERM', () => { server.close((err) => { if (err) { console.error(err) process.exit(1) } process.exit(0) }) }) ``` ## 3. Run Run the development server locally. Then, access `http://localhost:3000` in your Web browser. ::: code-group ```sh [npm] npm run dev ``` ```sh [yarn] yarn dev ``` ```sh [pnpm] pnpm dev ``` ::: ## Change port number You can specify the port number with the `port` option. ```ts serve({ fetch: app.fetch, port: 8787, }) ``` ## Access the raw Node.js APIs You can access the Node.js APIs from `c.env.incoming` and `c.env.outgoing`. ```ts import { Hono } from 'hono' import { serve, type HttpBindings } from '@hono/node-server' // or `Http2Bindings` if you use HTTP2 type Bindings = HttpBindings & { /* ... */ } const app = new Hono<{ Bindings: Bindings }>() app.get('/', (c) => { return c.json({ remoteAddress: c.env.incoming.socket.remoteAddress, }) }) serve(app) ``` ## Serve static files You can use `serveStatic` to serve static files from the local file system. For example, suppose the directory structure is as follows: ```sh ./ ├── favicon.ico ├── index.ts └── static ├── hello.txt └── image.png ``` If a request to the path `/static/*` comes in and you want to return a file under `./static`, you can write the following: ```ts import { serveStatic } from '@hono/node-server/serve-static' app.use('/static/*', serveStatic({ root: './' })) ``` ::: warning The `root` option resolves paths relative to the current working directory (`process.cwd()`). This means the behavior depends on **where you run your Node.js process from**, not where your source file is located. If you start your server from a different directory, file resolution may fail. For reliable path resolution that always points to the same directory as your source file, use `import.meta.url`: ```ts import { fileURLToPath } from 'node:url' import { serveStatic } from '@hono/node-server/serve-static' app.use( '/static/*', serveStatic({ root: fileURLToPath(new URL('./', import.meta.url)) }) ) ``` ::: Use the `path` option to serve `favicon.ico` in the directory root: ```ts app.use('/favicon.ico', serveStatic({ path: './favicon.ico' })) ``` If a request to the path `/hello.txt` or `/image.png` comes in and you want to return a file named `./static/hello.txt` or `./static/image.png`, you can use the following: ```ts app.use('*', serveStatic({ root: './static' })) ``` ### `rewriteRequestPath` If you want to map `http://localhost:3000/static/*` to `./statics`, you can use the `rewriteRequestPath` option: ```ts app.get( '/static/*', serveStatic({ root: './', rewriteRequestPath: (path) => path.replace(/^\/static/, '/statics'), }) ) ``` ## http2 You can run hono on a [Node.js http2 Server](https://nodejs.org/api/http2.html). ### unencrypted http2 ```ts import { createServer } from 'node:http2' const server = serve({ fetch: app.fetch, createServer, }) ``` ### encrypted http2 ```ts import { createSecureServer } from 'node:http2' import { readFileSync } from 'node:fs' const server = serve({ fetch: app.fetch, createServer: createSecureServer, serverOptions: { key: readFileSync('localhost-privkey.pem'), cert: readFileSync('localhost-cert.pem'), }, }) ``` ## Building & Deployment ::: code-group ```sh [npm] npm run build ``` ```sh [yarn] yarn run build ``` ```sh [pnpm] pnpm run build ``` ```sh [bun] bun run build ``` ::: info Apps with a front-end framework may need to use [Hono's Vite plugins](https://github.com/honojs/vite-plugins). ::: ### Dockerfile Here is an example of a Node.js Dockerfile. ```Dockerfile FROM node:22-alpine AS base FROM base AS builder RUN apk add --no-cache gcompat WORKDIR /app COPY package*json tsconfig.json src ./ RUN npm ci && \ npm run build && \ npm prune --production FROM base AS runner WORKDIR /app RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 hono COPY --from=builder --chown=hono:nodejs /app/node_modules /app/node_modules COPY --from=builder --chown=hono:nodejs /app/dist /app/dist COPY --from=builder --chown=hono:nodejs /app/package.json /app/package.json USER hono EXPOSE 3000 CMD ["node", "/app/dist/index.js"] ``` # Cloudflare Workers [Cloudflare Workers](https://workers.cloudflare.com) is a JavaScript edge runtime on Cloudflare CDN. You can develop the application locally and publish it with a few commands using [Wrangler](https://developers.cloudflare.com/workers/wrangler/). Wrangler includes transcompiler, so we can write the code with TypeScript. Let’s make your first application for Cloudflare Workers with Hono. ## 1. Setup A starter for Cloudflare Workers is available. Start your project with "create-hono" command. Select `cloudflare-workers` template for this example. ::: code-group ```sh [npm] npm create hono@latest my-app ``` ```sh [yarn] yarn create hono my-app ``` ```sh [pnpm] pnpm create hono my-app ``` ```sh [bun] bun create hono@latest my-app ``` ```sh [deno] deno init --npm hono my-app ``` ::: Move to `my-app` and install the dependencies. ::: code-group ```sh [npm] cd my-app npm i ``` ```sh [yarn] cd my-app yarn ``` ```sh [pnpm] cd my-app pnpm i ``` ```sh [bun] cd my-app bun i ``` ::: ## 2. Hello World Edit `src/index.ts` like below. ```ts import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Hello Cloudflare Workers!')) export default app ``` ## 3. Run Run the development server locally. Then, access `http://localhost:8787` in your web browser. ::: code-group ```sh [npm] npm run dev ``` ```sh [yarn] yarn dev ``` ```sh [pnpm] pnpm dev ``` ```sh [bun] bun run dev ``` ::: ### Change port number If you need to change the port number you can follow the instructions here to update `wrangler.toml` / `wrangler.json` / `wrangler.jsonc` files: [Wrangler Configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#local-development-settings) Or, you can follow the instructions here to set CLI options: [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/commands/#dev) ## 4. Deploy If you have a Cloudflare account, you can deploy to Cloudflare. In `package.json`, `$npm_execpath` needs to be changed to your package manager of choice. ::: code-group ```sh [npm] npm run deploy ``` ```sh [yarn] yarn deploy ``` ```sh [pnpm] pnpm run deploy ``` ```sh [bun] bun run deploy ``` ::: That's all! ## Using Hono with other event handlers You can integrate Hono with other event handlers (such as `scheduled`) in _Module Worker mode_. To do this, export `app.fetch` as the module's `fetch` handler, and then implement other handlers as needed: ```ts const app = new Hono() export default { fetch: app.fetch, scheduled: async (batch, env) => {}, } ``` ## Serve static files If you want to serve static files, you can use [the Static Assets feature](https://developers.cloudflare.com/workers/static-assets/) of Cloudflare Workers. Specify the directory for the files in `wrangler.toml`: ```toml assets = { directory = "public" } ``` Then create the `public` directory and place the files there. For instance, `./public/static/hello.txt` will be served as `/static/hello.txt`. ``` . ├── package.json ├── public │   ├── favicon.ico │   └── static │   └── hello.txt ├── src │   └── index.ts └── wrangler.toml ``` ## Types You have to install `@cloudflare/workers-types` if you want to have workers types. ::: code-group ```sh [npm] npm i --save-dev @cloudflare/workers-types ``` ```sh [yarn] yarn add -D @cloudflare/workers-types ``` ```sh [pnpm] pnpm add -D @cloudflare/workers-types ``` ```sh [bun] bun add --dev @cloudflare/workers-types ``` ::: ## Testing For testing, we recommend using `@cloudflare/vitest-pool-workers`. Refer to [examples](https://github.com/honojs/examples) for setting it up. If there is the application below. ```ts import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Please test me!')) ``` We can test if it returns "_200 OK_" Response with this code. ```ts describe('Test the application', () => { it('Should return 200 response', async () => { const res = await app.request('http://localhost/') expect(res.status).toBe(200) }) }) ``` ## Bindings In the Cloudflare Workers, we can bind the environment values, KV namespace, R2 bucket, or Durable Object. You can access them in `c.env`. It will have the types if you pass the "_type definition_" for the bindings to the `Hono` as generics. ```ts type Bindings = { MY_BUCKET: R2Bucket USERNAME: string PASSWORD: string } const app = new Hono<{ Bindings: Bindings }>() // Access to environment values app.put('/upload/:key', async (c, next) => { const key = c.req.param('key') await c.env.MY_BUCKET.put(key, c.req.body) return c.text(`Put ${key} successfully!`) }) ``` ## Using Variables in Middleware This is the only case for Module Worker mode. If you want to use Variables or Secret Variables in Middleware, for example, "username" or "password" in Basic Authentication Middleware, you need to write like the following. ```ts import { basicAuth } from 'hono/basic-auth' type Bindings = { USERNAME: string PASSWORD: string } const app = new Hono<{ Bindings: Bindings }>() //... app.use('/auth/*', async (c, next) => { const auth = basicAuth({ username: c.env.USERNAME, password: c.env.PASSWORD, }) return auth(c, next) }) ``` The same is applied to Bearer Authentication Middleware, JWT Authentication, or others. ## Deploy from GitHub Actions Before deploying code to Cloudflare via CI, you need a Cloudflare token. You can manage it from [User API Tokens](https://dash.cloudflare.com/profile/api-tokens). If it's a newly created token, select the **Edit Cloudflare Workers** template. If you already have another token, make sure the token has the corresponding permissions. (Note: token permissions are not shared between Cloudflare Pages and Cloudflare Workers). then go to your GitHub repository settings dashboard: `Settings->Secrets and variables->Actions->Repository secrets`, and add a new secret with the name `CLOUDFLARE_API_TOKEN`. then create `.github/workflows/deploy.yml` in your Hono project root folder, paste the following code: ```yml name: Deploy on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest name: Deploy steps: - uses: actions/checkout@v4 - name: Deploy uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} ``` then edit `wrangler.toml`, and add this code after `compatibility_date` line. ```toml main = "src/index.ts" minify = true ``` Everything is ready! Now push the code and enjoy it. ## Load env when local development To configure the environment variables for local development, create a `.dev.vars` file or a `.env` file in the root directory of the project. These files should be formatted using the [dotenv](https://hexdocs.pm/dotenvy/dotenv-file-format.html) syntax. For example: ``` SECRET_KEY=value API_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ``` > For more about this section you can find in the Cloudflare documentation: > https://developers.cloudflare.com/workers/wrangler/configuration/#secrets Then we use the `c.env.*` to get the environment variables in our code. ::: info By default, `process.env` is not available in Cloudflare Workers, so it is recommended to get environment variables from `c.env`. If you want to use it, you need to enable [`nodejs_compat_populate_process_env`](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#enable-auto-populating-processenv) flag. You can also import `env` from `cloudflare:workers`. For details, please see [How to access `env` on Cloudflare docs](https://developers.cloudflare.com/workers/runtime-apis/bindings/#how-to-access-env) ::: ```ts type Bindings = { SECRET_KEY: string } const app = new Hono<{ Bindings: Bindings }>() app.get('/env', (c) => { const SECRET_KEY = c.env.SECRET_KEY return c.text(SECRET_KEY) }) ``` Before you deploy your project to Cloudflare, remember to set the environment variable/secrets in the Cloudflare Workers project's configuration. > For more about this section you can find in the Cloudflare documentation: > https://developers.cloudflare.com/workers/configuration/environment-variables/#add-environment-variables-via-the-dashboard # Cloudflare Pages [Cloudflare Pages](https://pages.cloudflare.com) is an edge platform for full-stack web applications. It serves static files and dynamic content provided by Cloudflare Workers. Hono fully supports Cloudflare Pages. It introduces a delightful developer experience. Vite's dev server is fast, and deploying with Wrangler is super quick. ## 1. Setup A starter for Cloudflare Pages is available. Start your project with "create-hono" command. Select `cloudflare-pages` template for this example. ::: code-group ```sh [npm] npm create hono@latest my-app ``` ```sh [yarn] yarn create hono my-app ``` ```sh [pnpm] pnpm create hono my-app ``` ```sh [bun] bun create hono@latest my-app ``` ```sh [deno] deno init --npm hono my-app ``` ::: Move into `my-app` and install the dependencies. ::: code-group ```sh [npm] cd my-app npm i ``` ```sh [yarn] cd my-app yarn ``` ```sh [pnpm] cd my-app pnpm i ``` ```sh [bun] cd my-app bun i ``` ::: Below is a basic directory structure. ```text ./ ├── package.json ├── public │   └── static // Put your static files. │   └── style.css // You can refer to it as `/static/style.css`. ├── src │   ├── index.tsx // The entry point for server-side. │   └── renderer.tsx ├── tsconfig.json └── vite.config.ts ``` ## 2. Hello World Edit `src/index.tsx` like the following: ```tsx import { Hono } from 'hono' import { renderer } from './renderer' const app = new Hono() app.get('*', renderer) app.get('/', (c) => { return c.render(

Hello, Cloudflare Pages!

) }) export default app ``` ## 3. Run Run the development server locally. Then, access `http://localhost:5173` in your Web browser. ::: code-group ```sh [npm] npm run dev ``` ```sh [yarn] yarn dev ``` ```sh [pnpm] pnpm dev ``` ```sh [bun] bun run dev ``` ::: ## 4. Deploy If you have a Cloudflare account, you can deploy to Cloudflare. In `package.json`, `$npm_execpath` needs to be changed to your package manager of choice. ::: code-group ```sh [npm] npm run deploy ``` ```sh [yarn] yarn deploy ``` ```sh [pnpm] pnpm run deploy ``` ```sh [bun] bun run deploy ``` ::: ### Deploy via the Cloudflare dashboard with GitHub 1. Log in to the [Cloudflare dashboard](https://dash.cloudflare.com) and select your account. 2. In Account Home, select Workers & Pages > Create application > Pages > Connect to Git. 3. Authorize your GitHub account, and select the repository. In Set up builds and deployments, provide the following information: | Configuration option | Value | | -------------------- | --------------- | | Production branch | `main` | | Build command | `npm run build` | | Build directory | `dist` | ## Bindings You can use Cloudflare Bindings like Variables, KV, D1, and others. In this section, let's use Variables and KV. ### Create `wrangler.toml` First, create `wrangler.toml` for local Bindings: ```sh touch wrangler.toml ``` Edit `wrangler.toml`. Specify Variable with the name `MY_NAME`. ```toml [vars] MY_NAME = "Hono" ``` ### Create KV Next, make the KV. Run the following `wrangler` command: ```sh wrangler kv namespace create MY_KV --preview ``` Note down the `preview_id` as the following output: ``` { binding = "MY_KV", preview_id = "abcdef" } ``` Specify `preview_id` with the name of Bindings, `MY_KV`: ```toml [[kv_namespaces]] binding = "MY_KV" id = "abcdef" ``` ### Edit `vite.config.ts` Edit the `vite.config.ts`: ```ts import devServer from '@hono/vite-dev-server' import adapter from '@hono/vite-dev-server/cloudflare' import build from '@hono/vite-cloudflare-pages' import { defineConfig } from 'vite' export default defineConfig({ plugins: [ devServer({ entry: 'src/index.tsx', adapter, // Cloudflare Adapter }), build(), ], }) ``` ### Use Bindings in your application Use Variable and KV in your application. Set the types. ```ts type Bindings = { MY_NAME: string MY_KV: KVNamespace } const app = new Hono<{ Bindings: Bindings }>() ``` Use them: ```tsx app.get('/', async (c) => { await c.env.MY_KV.put('name', c.env.MY_NAME) const name = await c.env.MY_KV.get('name') return c.render(

Hello! {name}

) }) ``` ### In production For Cloudflare Pages, you will use `wrangler.toml` for local development, but for production, you will set up Bindings in the dashboard. ## Client-side You can write client-side scripts and import them into your application using Vite's features. If `/src/client.ts` is the entry point for the client, simply write it in the script tag. Additionally, `import.meta.env.PROD` is useful for detecting whether it's running on a dev server or in the build phase. ```tsx app.get('/', (c) => { return c.html( {import.meta.env.PROD ? ( ) : ( )}

Hello

) }) ``` In order to build the script properly, you can use the example config file `vite.config.ts` as shown below. ```ts import pages from '@hono/vite-cloudflare-pages' import devServer from '@hono/vite-dev-server' import { defineConfig } from 'vite' export default defineConfig(({ mode }) => { if (mode === 'client') { return { build: { rollupOptions: { input: './src/client.ts', output: { entryFileNames: 'static/client.js', }, }, }, } } else { return { plugins: [ pages(), devServer({ entry: 'src/index.tsx', }), ], } } }) ``` You can run the following command to build the server and client script. ```sh vite build --mode client && vite build ``` ## Cloudflare Pages Middleware Cloudflare Pages uses its own [middleware](https://developers.cloudflare.com/pages/functions/middleware/) system that is different from Hono's middleware. You can enable it by exporting `onRequest` in a file named `_middleware.ts` like this: ```ts // functions/_middleware.ts export async function onRequest(pagesContext) { console.log(`You are accessing ${pagesContext.request.url}`) return await pagesContext.next() } ``` Using `handleMiddleware`, you can use Hono's middleware as Cloudflare Pages middleware. ```ts // functions/_middleware.ts import { handleMiddleware } from 'hono/cloudflare-pages' export const onRequest = handleMiddleware(async (c, next) => { console.log(`You are accessing ${c.req.url}`) await next() }) ``` You can also use built-in and 3rd party middleware for Hono. For example, to add Basic Authentication, you can use [Hono's Basic Authentication Middleware](/docs/middleware/builtin/basic-auth). ```ts // functions/_middleware.ts import { handleMiddleware } from 'hono/cloudflare-pages' import { basicAuth } from 'hono/basic-auth' export const onRequest = handleMiddleware( basicAuth({ username: 'hono', password: 'acoolproject', }) ) ``` If you want to apply multiple middleware, you can write it like this: ```ts import { handleMiddleware } from 'hono/cloudflare-pages' // ... export const onRequest = [ handleMiddleware(middleware1), handleMiddleware(middleware2), handleMiddleware(middleware3), ] ``` ### Accessing `EventContext` You can access [`EventContext`](https://developers.cloudflare.com/pages/functions/api-reference/#eventcontext) object via `c.env` in `handleMiddleware`. ```ts // functions/_middleware.ts import { handleMiddleware } from 'hono/cloudflare-pages' export const onRequest = [ handleMiddleware(async (c, next) => { c.env.eventContext.data.user = 'Joe' await next() }), ] ``` Then, you can access the data value in via `c.env.eventContext` in the handler: ```ts // functions/api/[[route]].ts import type { EventContext } from 'hono/cloudflare-pages' import { handle } from 'hono/cloudflare-pages' // ... type Env = { Bindings: { eventContext: EventContext } } const app = new Hono().basePath('/api') app.get('/hello', (c) => { return c.json({ message: `Hello, ${c.env.eventContext.data.user}!`, // 'Joe' }) }) export const onRequest = handle(app) ``` # Netlify Netlify provides static site hosting and serverless backend services. [Edge Functions](https://docs.netlify.com/edge-functions/overview/) enables us to make the web pages dynamic. Edge Functions support writing in Deno and TypeScript, and deployment is made easy through the [Netlify CLI](https://docs.netlify.com/cli/get-started/). With Hono, you can create the application for Netlify Edge Functions. ## 1. Setup A starter for Netlify is available. Start your project with "create-hono" command. Select `netlify` template for this example. ::: code-group ```sh [npm] npm create hono@latest my-app ``` ```sh [yarn] yarn create hono my-app ``` ```sh [pnpm] pnpm create hono my-app ``` ```sh [bun] bun create hono@latest my-app ``` ```sh [deno] deno init --npm hono my-app ``` ::: Move into `my-app`. ## 2. Hello World Edit `netlify/edge-functions/index.ts`: ```ts import { Hono } from 'jsr:@hono/hono' import { handle } from 'jsr:@hono/hono/netlify' const app = new Hono() app.get('/', (c) => { return c.text('Hello Hono!') }) export default handle(app) ``` ## 3. Run Run the development server with Netlify CLI. Then, access `http://localhost:8888` in your Web browser. ```sh netlify dev ``` ## 4. Deploy You can deploy with a `netlify deploy` command. ```sh netlify deploy --prod ``` ## `Context` You can access the Netlify's `Context` through `c.env`: ```ts import { Hono } from 'jsr:@hono/hono' import { handle } from 'jsr:@hono/hono/netlify' // Import the type definition import type { Context } from 'https://edge.netlify.com/' export type Env = { Bindings: { context: Context } } const app = new Hono() app.get('/country', (c) => c.json({ 'You are in': c.env.context.geo.country?.name, }) ) export default handle(app) ``` # WebAssembly (w/ WASI) [WebAssembly][wasm-core] is a secure, sandboxed, portable runtime that runs inside and outside web browsers. In practice: - Languages (like JavaScript) _compile to_ WebAssembly (`.wasm` files) - WebAssembly runtimes (like [`wasmtime`][wasmtime] or [`jco`][jco]) enable _running_ WebAssembly binaries While core WebAssembly has _no_ access to things like the local filesystem or sockets, the [WebAssembly System Interface][wasi] steps in to enable defining a platform under WebAssebly workloads. This means that _with_ WASI, WebAssembly can operate on files, sockets, and much more. ::: info Want to peek at the WASI interface yourself? check out [`wasi:http`][wasi-http] ::: Support for WebAssembly w/ WASI in JS is powered by [StarlingMonkey][sm], and thanks to the focus on Web standards in both StarlingMonkey and Hono, **Hono works \*out of the box with WASI-enabled WebAssembly ecosystems.** [sm]: https://github.com/bytecodealliance/StarlingMonkey [wasm-core]: https://webassembly.org/ [wasi]: https://wasi.dev/ [bca]: https://bytecodealliance.org/ [wasi-http]: https://github.com/WebAssembly/wasi-http ## 1. Setup The WebAssembly JS ecosystem provides tooling to make it easy to get started building WASI-enabled WebAssembly components: - [StarlingMonkey][sm] is a fork of [SpiderMonkey][spidermonkey] that compiles to WebAssembly and enables components - [`componentize-js`][componentize-js] turns JavaScript ES modules into WebAssembly components - [`jco`][jco] is a multi-tool that builds components, generates types, and runs components in environments like Node.js or the browser ::: info WebAssembly has an open ecosystem and is open source, with core projects stewarded primarily by the [Bytecode Alliance][bca] and its members. New features, issues, pull requests and other types of contributions are always welcome. ::: While a starter for Hono on WebAssembly is not yet available, you can start a WebAssembly Hono project just like any other: ::: code-group ```sh [npm] mkdir my-app cd my-app npm init npm i hono npm i -D @bytecodealliance/jco @bytecodealliance/componentize-js @bytecodealliance/jco-std npm i -D rolldown ``` ````sh [yarn] mkdir my-app cd my-app npm init yarn add hono yarn add -D @bytecodealliance/jco @bytecodealliance/componentize-js @bytecodealliance/jco-std yarn add -D rolldown G``` ```sh [pnpm] mkdir my-app cd my-app pnpm init --init-type module pnpm add hono pnpm add -D @bytecodealliance/jco @bytecodealliance/componentize-js @bytecodealliance/jco-std pnpm add -D rolldown ```` ```sh [bun] mkdir my-app cd my-app npm init bun add hono bun add -D @bytecodealliance/jco @bytecodealliance/componentize-js @bytecodealliance/jco-std ``` ::: ::: info To ensure your project uses ES modules, ensure `type` is set to `"module"` in `package.json` ::: After entering the `my-app` folder, install dependencies, and initialize TypeScript: ::: code-group ```sh [npm] npm i npx tsc --init ``` ```sh [yarn] yarn yarn tsc --init ``` ```sh [pnpm] pnpm i pnpm exec --init ``` ```sh [bun] bun i ``` ::: Once you have a basic TypeScript configuration file (`tsconfig.json`), please ensure it has the following configuration: - `compilerOptions.module` set to `"nodenext"` Since `componentize-js` (and `jco` which re-uses it) supports only single JS files, bundling is necessary, so [`rolldown`][rolldown] can be used to create a single file bundle. A Rolldown configuration (`rolldown.config.mjs`) like the following can be used: ```js import { defineConfig } from 'rolldown' export default defineConfig({ input: 'src/component.ts', external: /wasi:.*/, output: { file: 'dist/component.js', format: 'esm', }, }) ``` ::: info Feel free to use any other bundlers that you're more comfortable with (`rolldown`, `esbuild`, `rollup`, etc) ::: [jco]: https://github.com/bytecodealliance/jco [componentize-js]: https://github.com/bytecodealliance/componentize-js [rolldown]: https://rolldown.rs [spidermonkey]: https://spidermonkey.dev/ ## 2. Set up WIT interface & dependencies [WebAssembly Inteface Types (WIT)][wit] is an Interface Definition Language ("IDL") that governs what functionality a WebAssembly component uses ("imports"), and what it provides ("exports"). Amongst the standardized WIT interfaces, [`wasi:http`][wasi-http] is for dealing with HTTP requests (whether it's receiving them or sending them out), and since we intend to make a web server, our component must declare the use of `wasi:http/incoming-handler` in it's [WIT world][wit-world]: First, let's set up the component's WIT world in a file called `wit/component.wit`: ```txt package example:hono; world component { export wasi:http/incoming-handler@0.2.6; } ``` Put simply, the WIT file above means that our component "providers" the functionality of "receiving"/"handling incoming" HTTP requests. The `wasi:http/incoming-handler` interface relies on upstream standardized WIT interfaces (specifications on how requests are structured, etc). To pull those third party (Bytecode Alliance maintained) WIT interaces, one tool we can use is [`wkg`][wkg]: ```sh wkg wit fetch ``` Once `wkg` has finished running, you should find your `wit` folder populated with a new `deps` folder alongside `component.wit`: ``` wit ├── component.wit └── deps ├── wasi-cli-0.2.6 │   └── package.wit ├── wasi-clocks-0.2.6 │   └── package.wit ├── wasi-http-0.2.6 │   └── package.wit ├── wasi-io-0.2.6 │   └── package.wit └── wasi-random-0.2.6 └── package.wit ``` [wkg]: https://github.com/bytecodealliance/wasm-pkg-tools [wit-world]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#wit-worlds [wit]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md ## 3. Hello Wasm To build a HTTP server in WebAssembly, we can make use of the [`jco-std`][jco-std] project, which contains helpers that make the experience very similar to the standard Hono experience. Let's fulfill our `component` world with a basic Hono application as a WebAssembly component in a file called `src/component.ts`: ```ts import { Hono } from 'hono' import { fire } from '@bytecodealliance/jco-std/wasi/0.2.6/http/adapters/hono/server' const app = new Hono() app.get('/hello', (c) => { return c.json({ message: 'Hello from WebAssembly!' }) }) fire(app) // Although we've called `fire()` with wasi HTTP configured for use above, // we still need to actually export the `wasi:http/incoming-handler` interface object, // as jco and componentize-js will be looking for the ES module export that matches the WASI interface. export { incomingHandler } from '@bytecodealliance/jco-std/wasi/0.2.6/http/adapters/hono/server' ``` ## 4. Build Since we're using Rolldown (and it's configured to handle TypeScript compilation), we can use it to build and bundle: ::: code-group ```sh [npm] npx rolldown -c ``` ```sh [yarn] yarn rolldown -c ``` ```sh [pnpm] pnpm exec rolldown -c ``` ```sh [bun] bun build --target=bun --outfile=dist/component.js ./src/component.ts ``` ::: ::: info The bundling step is necessary because WebAssembly JS ecosystem tooling only currently supports a single JS file, and we'd like to include Hono along with related libraries. For components with simpler requirements, bundlers are not necessary. ::: To build your WebAssembly component, use `jco` (and indirectly `componentize-js`): ::: code-group ```sh [npm] npx jco componentize -w wit -o dist/component.wasm dist/component.js ``` ```sh [yarn] yarn jco componentize -w wit -o dist/component.wasm dist/component.js ``` ```sh [pnpm] pnpm exec jco componentize -w wit -o dist/component.wasm dist/component.js ``` ```sh [bun] bun run jco componentize -w wit -o dist/component.wasm dist/component.js ``` ::: ## 3. Run To run your Hono WebAssembly HTTP server, you can use any WASI-enabled WebAssembly runtime: - [`wasmtime`][wasmtime] - `jco` (runs in Node.js) In this guide, we'll use `jco serve` since it's already installed. ::: warning `jco serve` is meant for development, and is not recommended for production use. ::: [wasmtime]: https://wasmtime.dev ::: code-group ```sh [npm] npx jco serve dist/component.wasm ``` ```sh [yarn] yarn jco serve dist/component.wasm ``` ```sh [pnpm] pnpm exec jco serve dist/component.wasm ``` ```sh [bun] bun run jco serve dist/component.wasm ``` ::: You should see output like the following: ``` $ npx jco serve dist/component.wasm Server listening @ localhost:8000... ``` Sending a request to `localhost:8000/hello` will produce the JSON output you've specified in your Hono application. You should see output like the following: ```json { "message": "Hello from WebAssembly!" } ``` ::: info `jco serve` works by converting the WebAssembly component into a basic WebAssembly coremodule, so that it can be run in runtimes like Node.js and the browser. This process is normally run via `jco transpile`, and is the way we can use JS engines like Node.js and the browser (which may use V8 or other Javascript engines) as WebAssembly Component runtimes. How `jco transpile` is outside the scope of this guide, you can read more about it in [the Jco book][jco-book] ::: ## More information To learn more about WASI, WebAssembly components and more, see the following resources: - [BytecodeAlliance Component Model book][cm-book] - [`jco` codebase][jco] - [`jco` example components][jco-example-components] (in particular the [Hono example][jco-example-component-hono]) - [Jco book][jco-book] - [`componentize-js` codebase][componentize-js] - [StarlingMonkey codebase][sm] To reach out to the WebAssembly community with questions, comments, contributions or to file issues: - [Bytecode Alliance Zulip](https://bytecodealliance.zulipchat.com) (consider posting in the [#jco channel](https://bytecodealliance.zulipchat.com/#narrow/channel/409526-jco)) - [Jco repository](https://github.com/bytecodealliance/jco) - [componentize-js repository](https://github.com/bytecodealliance/componentize-js) [cm-book]: https://component-model.bytecodealliance.org/ [jco-book]: https://bytecodealliance.github.io/jco/ [jco-example-components]: https://github.com/bytecodealliance/jco/tree/main/examples/components [jco-example-component-hono]: https://github.com/bytecodealliance/jco/tree/main/examples/components/http-server-hono # Fastly Compute [Fastly Compute](https://www.fastly.com/products/edge-compute) is an advanced edge computing system that runs your code, in your favorite language, on Fastly's global edge network. Hono also works on Fastly Compute. You can develop the application locally and publish it with a few commands using [Fastly CLI](https://www.fastly.com/documentation/reference/tools/cli/), which is installed locally automatically as part of the template. ## 1. Setup A starter for Fastly Compute is available. Start your project with "create-hono" command. Select `fastly` template for this example. ::: code-group ```sh [npm] npm create hono@latest my-app ``` ```sh [yarn] yarn create hono my-app ``` ```sh [pnpm] pnpm create hono my-app ``` ```sh [bun] bun create hono@latest my-app ``` ```sh [deno] deno init --npm hono my-app ``` ::: Move to `my-app` and install the dependencies. ::: code-group ```sh [npm] cd my-app npm i ``` ```sh [yarn] cd my-app yarn ``` ```sh [pnpm] cd my-app pnpm i ``` ```sh [bun] cd my-app bun i ``` ::: ## 2. Hello World Edit `src/index.ts`: ```ts // src/index.ts import { Hono } from 'hono' import { fire } from '@fastly/hono-fastly-compute' const app = new Hono() app.get('/', (c) => c.text('Hello Fastly!')) fire(app) ``` > [!NOTE] > When using `fire` (or `buildFire()`) from `@fastly/hono-fastly-compute'` at the top level of your application, it is suitable to use `Hono` from `'hono'` rather than `'hono/quick'`, because `fire` causes its router to build its internal data during the application initialization phase. ## 3. Run Run the development server locally. Then, access `http://localhost:7676` in your Web browser. ::: code-group ```sh [npm] npm run start ``` ```sh [yarn] yarn start ``` ```sh [pnpm] pnpm run start ``` ```sh [bun] bun run start ``` ::: ## 4. Deploy To build and deploy your application to your Fastly account, type the following command. The first time you deploy the application, you will be prompted to create a new service in your account. If you don't have an account yet, you must [create your Fastly account](https://www.fastly.com/signup/). ::: code-group ```sh [npm] npm run deploy ``` ```sh [yarn] yarn deploy ``` ```sh [pnpm] pnpm run deploy ``` ```sh [bun] bun run deploy ``` ::: ## Bindings In Fastly Compute, you can bind Fastly platform resources, such as KV Stores, Config Stores, Secret Stores, Backends, Access Control Lists, Named Log Streams, and Environment Variables. You can access them through `c.env`, and will have their individual SDK types. To use these bindings, import `buildFire` instead of `fire` from `@fastly/hono-fastly-compute`. Define your [bindings](https://github.com/fastly/compute-js-context?tab=readme-ov-file#typed-bindings-with-buildcontextproxy) and pass them to [`buildFire()`](https://github.com/fastly/hono-fastly-compute?tab=readme-ov-file#basic-example) to obtain `fire`. Then use `fire.Bindings` to define your `Env` type as you construct `Hono`. ```ts // src/index.ts import { buildFire } from '@fastly/hono-fastly-compute' const fire = buildFire({ siteData: 'KVStore:site-data', // I have a KV Store named "site-data" }) const app = new Hono<{ Bindings: typeof fire.Bindings }>() app.put('/upload/:key', async (c, next) => { // e.g., Access the KV Store const key = c.req.param('key') await c.env.siteData.put(key, c.req.body) return c.text(`Put ${key} successfully!`) }) fire(app) ``` # Next.js Next.js is a flexible React framework that gives you building blocks to create fast web applications. You can run Hono on Next.js when using the Node.js runtime.\ On Vercel, deploying Hono with Next.js is easy by using Vercel Functions. ## 1. Setup A starter for Next.js is available. Start your project with "create-hono" command. Select `nextjs` template for this example. ::: code-group ```sh [npm] npm create hono@latest my-app ``` ```sh [yarn] yarn create hono my-app ``` ```sh [pnpm] pnpm create hono my-app ``` ```sh [bun] bun create hono@latest my-app ``` ```sh [deno] deno init --npm hono my-app ``` ::: Move into `my-app` and install the dependencies. ::: code-group ```sh [npm] cd my-app npm i ``` ```sh [yarn] cd my-app yarn ``` ```sh [pnpm] cd my-app pnpm i ``` ```sh [bun] cd my-app bun i ``` ::: ## 2. Hello World If you use the App Router, Edit `app/api/[[...route]]/route.ts`. Refer to the [Supported HTTP Methods](https://nextjs.org/docs/app/building-your-application/routing/route-handlers#supported-http-methods) section for more options. ```ts import { Hono } from 'hono' import { handle } from 'hono/vercel' const app = new Hono().basePath('/api') app.get('/hello', (c) => { return c.json({ message: 'Hello Next.js!', }) }) export const GET = handle(app) export const POST = handle(app) ``` ## 3. Run Run the development server locally. Then, access `http://localhost:3000` in your Web browser. ::: code-group ```sh [npm] npm run dev ``` ```sh [yarn] yarn dev ``` ```sh [pnpm] pnpm dev ``` ```sh [bun] bun run dev ``` ::: Now, `/api/hello` just returns JSON, but if you build React UIs, you can create a full-stack application with Hono. ## 4. Deploy If you have a Vercel account, you can deploy by linking the Git repository. ## Pages Router If you use the Pages Router, you'll need to install the Node.js adapter first. ::: code-group ```sh [npm] npm i @hono/node-server ``` ```sh [yarn] yarn add @hono/node-server ``` ```sh [pnpm] pnpm add @hono/node-server ``` ```sh [bun] bun add @hono/node-server ``` ::: Then, you can utilize the `handle` function imported from `@hono/node-server/vercel` in `pages/api/[[...route]].ts`. ```ts import { Hono } from 'hono' import { handle } from '@hono/node-server/vercel' import type { PageConfig } from 'next' export const config: PageConfig = { api: { bodyParser: false, }, } const app = new Hono().basePath('/api') app.get('/hello', (c) => { return c.json({ message: 'Hello Next.js!', }) }) export default handle(app) ``` In order for this to work with the Pages Router, it's important to disable Vercel Node.js helpers by setting up an environment variable in your project dashboard or in your `.env` file. ```text NODEJS_HELPERS=0 ``` # API Hono's API is simple. Just composed by extended objects from Web Standards. So, you can understand it well quickly. In this section, we introduce API of Hono like below. - Hono object - About routing - Context object - About middleware # Context The `Context` object is instantiated for each request and kept until the response is returned. You can put values in it, set headers and a status code you want to return, and access HonoRequest and Response objects. ## req `req` is an instance of HonoRequest. For more details, see [HonoRequest](/docs/api/request). ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/hello', (c) => { const userAgent = c.req.header('User-Agent') // ... // ---cut-start--- return c.text(`Hello, ${userAgent}`) // ---cut-end--- }) ``` ## status() You can set an HTTP status code with `c.status()`. The default is `200`. You don't have to use `c.status()` if the code is `200`. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.post('/posts', (c) => { // Set HTTP status code c.status(201) return c.text('Your post is created!') }) ``` ## header() You can set HTTP Headers for the response. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/', (c) => { // Set headers c.header('X-Message', 'My custom message') return c.text('Hello!') }) ``` ## body() Return an HTTP response. ::: info **Note**: When returning text or HTML, it is recommended to use `c.text()` or `c.html()`. ::: ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/welcome', (c) => { c.header('Content-Type', 'text/plain') // Return the response body return c.body('Thank you for coming') }) ``` You can also write the following. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/welcome', (c) => { return c.body('Thank you for coming', 201, { 'X-Message': 'Hello!', 'Content-Type': 'text/plain', }) }) ``` The response is the same `Response` object as below. ```ts twoslash new Response('Thank you for coming', { status: 201, headers: { 'X-Message': 'Hello!', 'Content-Type': 'text/plain', }, }) ``` ## text() Render text as `Content-Type: text/plain`. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/say', (c) => { return c.text('Hello!') }) ``` ## json() Render JSON as `Content-Type: application/json`. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/api', (c) => { return c.json({ message: 'Hello!' }) }) ``` ## html() Render HTML as `Content-Type: text/html`. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/', (c) => { return c.html('

Hello! Hono!

') }) ``` ## notFound() Return a `Not Found` Response. You can customize it with [`app.notFound()`](/docs/api/hono#not-found). ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/notfound', (c) => { return c.notFound() }) ``` ## redirect() Redirect, default status code is `302`. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/redirect', (c) => { return c.redirect('/') }) app.get('/redirect-permanently', (c) => { return c.redirect('/', 301) }) ``` ## res You can access the [Response] object that will be returned. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- // Response object app.use('/', async (c, next) => { await next() c.res.headers.append('X-Debug', 'Debug message') }) ``` [Response]: https://developer.mozilla.org/en-US/docs/Web/API/Response ## set() / get() Get and set arbitrary key-value pairs, with a lifetime of the current request. This allows passing specific values between middleware or from middleware to route handlers. ```ts twoslash import { Hono } from 'hono' const app = new Hono<{ Variables: { message: string } }>() // ---cut--- app.use(async (c, next) => { c.set('message', 'Hono is cool!!') await next() }) app.get('/', (c) => { const message = c.get('message') return c.text(`The message is "${message}"`) }) ``` Pass the `Variables` as Generics to the constructor of `Hono` to make it type-safe. ```ts twoslash import { Hono } from 'hono' // ---cut--- type Variables = { message: string } const app = new Hono<{ Variables: Variables }>() ``` The value of `c.set` / `c.get` are retained only within the same request. They cannot be shared or persisted across different requests. ## var You can also access the value of a variable with `c.var`. ```ts twoslash import type { Context } from 'hono' declare const c: Context // ---cut--- const result = c.var.client.oneMethod() ``` If you want to create the middleware which provides a custom method, write like the following: ```ts twoslash import { Hono } from 'hono' import { createMiddleware } from 'hono/factory' // ---cut--- type Env = { Variables: { echo: (str: string) => string } } const app = new Hono() const echoMiddleware = createMiddleware(async (c, next) => { c.set('echo', (str) => str) await next() }) app.get('/echo', echoMiddleware, (c) => { return c.text(c.var.echo('Hello!')) }) ``` If you want to use the middleware in multiple handlers, you can use `app.use()`. Then, you have to pass the `Env` as Generics to the constructor of `Hono` to make it type-safe. ```ts twoslash import { Hono } from 'hono' import type { MiddlewareHandler } from 'hono/types' declare const echoMiddleware: MiddlewareHandler type Env = { Variables: { echo: (str: string) => string } } // ---cut--- const app = new Hono() app.use(echoMiddleware) app.get('/echo', (c) => { return c.text(c.var.echo('Hello!')) }) ``` ## render() / setRenderer() You can set a layout using `c.setRenderer()` within a custom middleware. ```tsx twoslash /** @jsx jsx */ /** @jsxImportSource hono/jsx */ import { Hono } from 'hono' const app = new Hono() // ---cut--- app.use(async (c, next) => { c.setRenderer((content) => { return c.html(

{content}

) }) await next() }) ``` Then, you can utilize `c.render()` to create responses within this layout. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/', (c) => { return c.render('Hello!') }) ``` The output of which will be: ```html

Hello!

``` Additionally, this feature offers the flexibility to customize arguments. To ensure type safety, types can be defined as: ```ts declare module 'hono' { interface ContextRenderer { ( content: string | Promise, head: { title: string } ): Response | Promise } } ``` Here's an example of how you can use this: ```ts app.use('/pages/*', async (c, next) => { c.setRenderer((content, head) => { return c.html( {head.title}
{head.title}

{content}

) }) await next() }) app.get('/pages/my-favorite', (c) => { return c.render(

Ramen and Sushi

, { title: 'My favorite', }) }) app.get('/pages/my-hobbies', (c) => { return c.render(

Watching baseball

, { title: 'My hobbies', }) }) ``` ## executionCtx You can access Cloudflare Workers' specific [ExecutionContext](https://developers.cloudflare.com/workers/runtime-apis/context/). ```ts twoslash import { Hono } from 'hono' const app = new Hono<{ Bindings: { KV: any } }>() declare const key: string declare const data: string // ---cut--- // ExecutionContext object app.get('/foo', async (c) => { c.executionCtx.waitUntil(c.env.KV.put(key, data)) // ... }) ``` The `ExecutionContext` also has an [`exports`](https://developers.cloudflare.com/workers/runtime-apis/context/#exports) field. To get autocomplete with Wrangler's generated types, you can use module augmentation: ```ts import 'hono' declare module 'hono' { interface ExecutionContext { readonly exports: Cloudflare.Exports } } ``` ## event You can access Cloudflare Workers' specific `FetchEvent`. This was used in "Service Worker" syntax. But, it is not recommended now. ```ts twoslash import { Hono } from 'hono' declare const key: string declare const data: string type KVNamespace = any // ---cut--- // Type definition to make type inference type Bindings = { MY_KV: KVNamespace } const app = new Hono<{ Bindings: Bindings }>() // FetchEvent object (only set when using Service Worker syntax) app.get('/foo', async (c) => { c.event.waitUntil(c.env.MY_KV.put(key, data)) // ... }) ``` ## env In Cloudflare Workers Environment variables, secrets, KV namespaces, D1 database, R2 bucket etc. that are bound to a worker are known as bindings. Regardless of type, bindings are always available as global variables and can be accessed via the context `c.env.BINDING_KEY`. ```ts twoslash import { Hono } from 'hono' type KVNamespace = any // ---cut--- // Type definition to make type inference type Bindings = { MY_KV: KVNamespace } const app = new Hono<{ Bindings: Bindings }>() // Environment object for Cloudflare Workers app.get('/', async (c) => { c.env.MY_KV.get('my-key') // ... }) ``` ## error If the Handler throws an error, the error object is placed in `c.error`. You can access it in your middleware. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.use(async (c, next) => { await next() if (c.error) { // do something... } }) ``` ## ContextVariableMap For instance, if you wish to add type definitions to variables when a specific middleware is used, you can extend `ContextVariableMap`. For example: ```ts declare module 'hono' { interface ContextVariableMap { result: string } } ``` You can then utilize this in your middleware: ```ts twoslash import { createMiddleware } from 'hono/factory' // ---cut--- const mw = createMiddleware(async (c, next) => { c.set('result', 'some values') // result is a string await next() }) ``` In a handler, the variable is inferred as the proper type: ```ts twoslash import { Hono } from 'hono' const app = new Hono<{ Variables: { result: string } }>() // ---cut--- app.get('/', (c) => { const val = c.get('result') // val is a string // ... return c.json({ result: val }) }) ``` # App - Hono `Hono` is the primary object. It will be imported first and used until the end. ```ts twoslash import { Hono } from 'hono' const app = new Hono() //... export default app // for Cloudflare Workers or Bun ``` ## Methods An instance of `Hono` has the following methods. - app.**HTTP_METHOD**(\[path,\]handler|middleware...) - app.**all**(\[path,\]handler|middleware...) - app.**on**(method|method[], path|path[], handler|middleware...) - app.**use**(\[path,\]middleware) - app.**route**(path, \[app\]) - app.**basePath**(path) - app.**notFound**(handler) - app.**onError**(err, handler) - app.**mount**(path, anotherApp) - app.**fire**() - app.**fetch**(request, env, event) - app.**request**(path, options) The first part of them is used for routing, please refer to the [routing section](/docs/api/routing). ## Not Found `app.notFound` allows you to customize a Not Found Response. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.notFound((c) => { return c.text('Custom 404 Message', 404) }) ``` :::warning The `notFound` method is only called from the top-level app. For more information, see this [issue](https://github.com/honojs/hono/issues/3465#issuecomment-2381210165). ::: ## Error Handling `app.onError` allows you to handle uncaught errors and return a custom Response. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.onError((err, c) => { console.error(`${err}`) return c.text('Custom Error Message', 500) }) ``` ::: info If both a parent app and its routes have `onError` handlers, the route-level handlers get priority. ::: ## fire() ::: warning **`app.fire()` is deprecated**. Use `fire()` from `hono/service-worker` instead. See the [Service Worker documentation](/docs/getting-started/service-worker) for details. ::: `app.fire()` automatically adds a global `fetch` event listener. This can be useful for environments that adhere to the [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), such as [non-ES module Cloudflare Workers](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/). `app.fire()` executes the following for you: ```ts addEventListener('fetch', (event: FetchEventLike): void => { event.respondWith(this.dispatch(...)) }) ``` ## fetch() `app.fetch` will be the entry point of your application. For Cloudflare Workers, you can use the following: ```ts twoslash import { Hono } from 'hono' const app = new Hono() type Env = any type ExecutionContext = any // ---cut--- export default { fetch(request: Request, env: Env, ctx: ExecutionContext) { return app.fetch(request, env, ctx) }, } ``` or just do: ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- export default app ``` Bun: ```ts export default app // [!code --] export default { // [!code ++] port: 3000, // [!code ++] fetch: app.fetch, // [!code ++] } // [!code ++] ``` ## request() `request` is a useful method for testing. You can pass a URL or pathname to send a GET request. `app` will return a `Response` object. ```ts twoslash import { Hono } from 'hono' const app = new Hono() declare const test: (name: string, fn: () => void) => void declare const expect: (value: any) => any // ---cut--- test('GET /hello is ok', async () => { const res = await app.request('/hello') expect(res.status).toBe(200) }) ``` You can also pass a `Request` object: ```ts twoslash import { Hono } from 'hono' const app = new Hono() declare const test: (name: string, fn: () => void) => void declare const expect: (value: any) => any // ---cut--- test('POST /message is ok', async () => { const req = new Request('Hello!', { method: 'POST', }) const res = await app.request(req) expect(res.status).toBe(201) }) ``` ## mount() The `mount()` allows you to mount applications built with other frameworks into your Hono application. ```ts import { Router as IttyRouter } from 'itty-router' import { Hono } from 'hono' // Create itty-router application const ittyRouter = IttyRouter() // Handle `GET /itty-router/hello` ittyRouter.get('/hello', () => new Response('Hello from itty-router')) // Hono application const app = new Hono() // Mount! app.mount('/itty-router', ittyRouter.handle) ``` ## strict mode Strict mode defaults to `true` and distinguishes the following routes. - `/hello` - `/hello/` `app.get('/hello')` will not match `GET /hello/`. By setting strict mode to `false`, both paths will be treated equally. ```ts twoslash import { Hono } from 'hono' // ---cut--- const app = new Hono({ strict: false }) ``` ## router option The `router` option specifies which router to use. The default router is `SmartRouter`. If you want to use `RegExpRouter`, pass it to a new `Hono` instance: ```ts twoslash import { Hono } from 'hono' // ---cut--- import { RegExpRouter } from 'hono/router/reg-exp-router' const app = new Hono({ router: new RegExpRouter() }) ``` ## Generics You can pass Generics to specify the types of Cloudflare Workers Bindings and variables used in `c.set`/`c.get`. ```ts twoslash import { Hono } from 'hono' type User = any declare const user: User // ---cut--- type Bindings = { TOKEN: string } type Variables = { user: User } const app = new Hono<{ Bindings: Bindings Variables: Variables }>() app.use('/auth/*', async (c, next) => { const token = c.env.TOKEN // token is `string` // ... c.set('user', user) // user should be `User` await next() }) ``` # Presets Hono has several routers, each designed for a specific purpose. You can specify the router you want to use in the constructor of Hono. **Presets** are provided for common use cases, so you don't have to specify the router each time. The `Hono` class imported from all presets is the same, the only difference being the router. Therefore, you can use them interchangeably. ## `hono` Usage: ```ts twoslash import { Hono } from 'hono' ``` Routers: ```ts this.router = new SmartRouter({ routers: [new RegExpRouter(), new TrieRouter()], }) ``` ## `hono/quick` Usage: ```ts twoslash import { Hono } from 'hono/quick' ``` Router: ```ts this.router = new SmartRouter({ routers: [new LinearRouter(), new TrieRouter()], }) ``` ## `hono/tiny` Usage: ```ts twoslash import { Hono } from 'hono/tiny' ``` Router: ```ts this.router = new PatternRouter() ``` ## Which preset should I use? | Preset | Suitable platforms | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `hono` | This is highly recommended for most use cases. Although the registration phase may be slower than `hono/quick`, it exhibits high performance once booted. It's ideal for long-life servers built with **Deno**, **Bun**, or **Node.js**. It is also suitable for **Fastly Compute**, as route registration occurs during the app build phase on that platform. For environments such as **Cloudflare Workers**, **Deno Deploy**, where v8 isolates are utilized, this preset is suitable as well. Because the isolations persist for a certain amount of time after booting. | | `hono/quick` | This preset is designed for environments where the application is initialized for every request. | | `hono/tiny` | This is the smallest router package and it's suitable for environments where resources are limited. | # Routing Routing of Hono is flexible and intuitive. Let's take a look. ## Basic ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- // HTTP Methods app.get('/', (c) => c.text('GET /')) app.post('/', (c) => c.text('POST /')) app.put('/', (c) => c.text('PUT /')) app.delete('/', (c) => c.text('DELETE /')) // Wildcard app.get('/wild/*/card', (c) => { return c.text('GET /wild/*/card') }) // Any HTTP methods app.all('/hello', (c) => c.text('Any Method /hello')) // Custom HTTP method app.on('PURGE', '/cache', (c) => c.text('PURGE Method /cache')) // Multiple Method app.on(['PUT', 'DELETE'], '/post', (c) => c.text('PUT or DELETE /post') ) // Multiple Paths app.on('GET', ['/hello', '/ja/hello', '/en/hello'], (c) => c.text('Hello') ) ``` ## Path Parameter ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/user/:name', async (c) => { const name = c.req.param('name') // ^? // ... }) ``` or all parameters at once: ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/posts/:id/comment/:comment_id', async (c) => { const { id, comment_id } = c.req.param() // ^? // ... }) ``` ## Optional Parameter ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- // Will match `/api/animal` and `/api/animal/:type` app.get('/api/animal/:type?', (c) => c.text('Animal!')) ``` ## Regexp ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/post/:date{[0-9]+}/:title{[a-z]+}', async (c) => { const { date, title } = c.req.param() // ^? // ... }) ``` ## Including slashes ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/posts/:filename{.+\\.png}', async (c) => { //... }) ``` ## Chained route ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app .get('/endpoint', (c) => { return c.text('GET /endpoint') }) .post((c) => { return c.text('POST /endpoint') }) .delete((c) => { return c.text('DELETE /endpoint') }) ``` ## Grouping You can group the routes with the Hono instance and add them to the main app with the route method. ```ts twoslash import { Hono } from 'hono' // ---cut--- const book = new Hono() book.get('/', (c) => c.text('List Books')) // GET /book book.get('/:id', (c) => { // GET /book/:id const id = c.req.param('id') return c.text('Get Book: ' + id) }) book.post('/', (c) => c.text('Create Book')) // POST /book const app = new Hono() app.route('/book', book) ``` ## Grouping without changing base You can also group multiple instances while keeping base. ```ts twoslash import { Hono } from 'hono' // ---cut--- const book = new Hono() book.get('/book', (c) => c.text('List Books')) // GET /book book.post('/book', (c) => c.text('Create Book')) // POST /book const user = new Hono().basePath('/user') user.get('/', (c) => c.text('List Users')) // GET /user user.post('/', (c) => c.text('Create User')) // POST /user const app = new Hono() app.route('/', book) // Handle /book app.route('/', user) // Handle /user ``` ## Base path You can specify the base path. ```ts twoslash import { Hono } from 'hono' // ---cut--- const api = new Hono().basePath('/api') api.get('/book', (c) => c.text('List Books')) // GET /api/book ``` ## Routing with hostname It works fine if it includes a hostname. ```ts twoslash import { Hono } from 'hono' // ---cut--- const app = new Hono({ getPath: (req) => req.url.replace(/^https?:\/([^?]+).*$/, '$1'), }) app.get('/www1.example.com/hello', (c) => c.text('hello www1')) app.get('/www2.example.com/hello', (c) => c.text('hello www2')) ``` ## Routing with `host` Header value Hono can handle the `host` header value if you set the `getPath()` function in the Hono constructor. ```ts twoslash import { Hono } from 'hono' // ---cut--- const app = new Hono({ getPath: (req) => '/' + req.headers.get('host') + req.url.replace(/^https?:\/\/[^/]+(\/[^?]*).*/, '$1'), }) app.get('/www1.example.com/hello', (c) => c.text('hello www1')) // A following request will match the route: // new Request('http://www1.example.com/hello', { // headers: { host: 'www1.example.com' }, // }) ``` By applying this, for example, you can change the routing by `User-Agent` header. ## Routing priority Handlers or middleware will be executed in registration order. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/book/a', (c) => c.text('a')) // a app.get('/book/:slug', (c) => c.text('common')) // common ``` ``` GET /book/a ---> `a` GET /book/b ---> `common` ``` When a handler is executed, the process will be stopped. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('*', (c) => c.text('common')) // common app.get('/foo', (c) => c.text('foo')) // foo ``` ``` GET /foo ---> `common` // foo will not be dispatched ``` If you have the middleware that you want to execute, write the code above the handler. ```ts twoslash import { Hono } from 'hono' import { logger } from 'hono/logger' const app = new Hono() // ---cut--- app.use(logger()) app.get('/foo', (c) => c.text('foo')) ``` If you want to have a "_fallback_" handler, write the code below the other handler. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/bar', (c) => c.text('bar')) // bar app.get('*', (c) => c.text('fallback')) // fallback ``` ``` GET /bar ---> `bar` GET /foo ---> `fallback` ``` ## Grouping ordering Note that the mistake of grouping routings is hard to notice. The `route()` function takes the stored routing from the second argument (such as `three` or `two`) and adds it to its own (`two` or `app`) routing. ```ts three.get('/hi', (c) => c.text('hi')) two.route('/three', three) app.route('/two', two) export default app ``` It will return 200 response. ``` GET /two/three/hi ---> `hi` ``` However, if they are in the wrong order, it will return a 404. ```ts twoslash import { Hono } from 'hono' const app = new Hono() const two = new Hono() const three = new Hono() // ---cut--- three.get('/hi', (c) => c.text('hi')) app.route('/two', two) // `two` does not have routes two.route('/three', three) export default app ``` ``` GET /two/three/hi ---> 404 Not Found ``` # HTTPException When a fatal error occurs, Hono (and many ecosystem middleware) may throw an `HTTPException`. This is a custom Hono `Error` that simplifies [returning error responses](#handling-httpexceptions). ## Throwing HTTPExceptions You can throw your own HTTPExceptions by specifying a status code, and either a message or a custom response. ### Custom Message For basic `text` responses, just set the error `message`. ```ts twoslash import { HTTPException } from 'hono/http-exception' throw new HTTPException(401, { message: 'Unauthorized' }) ``` ### Custom Response For other response types, or to set response headers, use the `res` option. _Note that the status passed to the constructor is the one used to create responses._ ```ts twoslash import { HTTPException } from 'hono/http-exception' const errorResponse = new Response('Unauthorized', { status: 401, // this gets ignored headers: { Authenticate: 'error="invalid_token"', }, }) throw new HTTPException(401, { res: errorResponse }) ``` ### Cause In either case, you can use the [`cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) option to add arbitrary data to the HTTPException. ```ts twoslash import { Hono, Context } from 'hono' import { HTTPException } from 'hono/http-exception' const app = new Hono() declare const message: string declare const authorize: (c: Context) => Promise // ---cut--- app.post('/login', async (c) => { try { await authorize(c) } catch (cause) { throw new HTTPException(401, { message, cause }) } return c.redirect('/') }) ``` ## Handling HTTPExceptions You can handle uncaught HTTPExceptions with [`app.onError`](/docs/api/hono#error-handling). They include a `getResponse` method that returns a new `Response` created from the error `status`, and either the error `message`, or the [custom response](#custom-response) set when the error was thrown. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- import { HTTPException } from 'hono/http-exception' // ... app.onError((err, c) => { if (err instanceof HTTPException) { // Return the error response generated by HTTPException return err.getResponse() } // For any other unexpected errors, log and return a generic 500 response console.error(err) return c.text('Internal Server Error', 500) }) ``` ::: warning **`HTTPException.getResponse` is not aware of `Context`**. To include headers already set in `Context`, you must apply them to a new `Response`. ::: # HonoRequest The `HonoRequest` is an object that can be taken from `c.req` which wraps a [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. ## param() Get the values of path parameters. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- // Captured params app.get('/entry/:id', async (c) => { const id = c.req.param('id') // ^? // ... }) // Get all params at once app.get('/entry/:id/comment/:commentId', async (c) => { const { id, commentId } = c.req.param() // ^? }) ``` ## query() Get querystring parameters. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- // Query params app.get('/search', async (c) => { const query = c.req.query('q') // ^? }) // Get all params at once app.get('/search', async (c) => { const { q, limit, offset } = c.req.query() // ^? }) ``` ## queries() Get multiple querystring parameter values, e.g. `/search?tags=A&tags=B` ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/search', async (c) => { // tags will be string[] const tags = c.req.queries('tags') // ^? // ... }) ``` ## header() Get the request header value. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/', (c) => { const userAgent = c.req.header('User-Agent') // ^? return c.text(`Your user agent is ${userAgent}`) }) ``` ::: warning When `c.req.header()` is called with no arguments, all keys in the returned record are **lowercase**. If you want to get the value of a header with an uppercase name, use `c.req.header(“X-Foo”)`. ```ts // ❌ Will not work const headerRecord = c.req.header() const foo = headerRecord['X-Foo'] // ✅ Will work const foo = c.req.header('X-Foo') ``` ::: ## parseBody() Parse Request body of type `multipart/form-data` or `application/x-www-form-urlencoded` ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.post('/entry', async (c) => { const body = await c.req.parseBody() // ... }) ``` `parseBody()` supports the following behaviors. **Single file** ```ts twoslash import { Context } from 'hono' declare const c: Context // ---cut--- const body = await c.req.parseBody() const data = body['foo'] // ^? ``` `body['foo']` is `(string | File)`. If multiple files are uploaded, the last one will be used. ### Multiple files ```ts twoslash import { Context } from 'hono' declare const c: Context // ---cut--- const body = await c.req.parseBody() body['foo[]'] ``` `body['foo[]']` is always `(string | File)[]`. `[]` postfix is required. ### Multiple files or fields with same name If you have an input field that allows multiple `` or multiple checkboxes with the same name ``. ```ts twoslash import { Context } from 'hono' declare const c: Context // ---cut--- const body = await c.req.parseBody({ all: true }) body['foo'] ``` `all` option is disabled by default. - If `body['foo']` is multiple files, it will be parsed to `(string | File)[]`. - If `body['foo']` is single file, it will be parsed to `(string | File)`. ### Dot notation If you set the `dot` option `true`, the return value is structured based on the dot notation. Imagine receiving the following data: ```ts twoslash const data = new FormData() data.append('obj.key1', 'value1') data.append('obj.key2', 'value2') ``` You can get the structured value by setting the `dot` option `true`: ```ts twoslash import { Context } from 'hono' declare const c: Context // ---cut--- const body = await c.req.parseBody({ dot: true }) // body is `{ obj: { key1: 'value1', key2: 'value2' } }` ``` ## json() Parses the request body of type `application/json` ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.post('/entry', async (c) => { const body = await c.req.json() // ... }) ``` ## text() Parses the request body of type `text/plain` ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.post('/entry', async (c) => { const body = await c.req.text() // ... }) ``` ## arrayBuffer() Parses the request body as an `ArrayBuffer` ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.post('/entry', async (c) => { const body = await c.req.arrayBuffer() // ... }) ``` ## blob() Parses the request body as a `Blob`. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.post('/entry', async (c) => { const body = await c.req.blob() // ... }) ``` ## formData() Parses the request body as a `FormData`. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.post('/entry', async (c) => { const body = await c.req.formData() // ... }) ``` ## valid() Get the validated data. ```ts app.post('/posts', async (c) => { const { title, body } = c.req.valid('form') // ... }) ``` Available targets are below. - `form` - `json` - `query` - `header` - `cookie` - `param` See the [Validation section](/docs/guides/validation) for usage examples. ## routePath ::: warning **Deprecated in v4.8.0**: This property is deprecated. Use `routePath()` from [Route Helper](/docs/helpers/route) instead. ::: You can retrieve the registered path within the handler like this: ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/posts/:id', (c) => { return c.json({ path: c.req.routePath }) }) ``` If you access `/posts/123`, it will return `/posts/:id`: ```json { "path": "/posts/:id" } ``` ## matchedRoutes ::: warning **Deprecated in v4.8.0**: This property is deprecated. Use `matchedRoutes()` from [Route Helper](/docs/helpers/route) instead. ::: It returns matched routes within the handler, which is useful for debugging. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.use(async function logger(c, next) { await next() c.req.matchedRoutes.forEach(({ handler, method, path }, i) => { const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]') console.log( method, ' ', path, ' '.repeat(Math.max(10 - path.length, 0)), name, i === c.req.routeIndex ? '<- respond from here' : '' ) }) }) ``` ## path The request pathname. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/about/me', async (c) => { const pathname = c.req.path // `/about/me` // ... }) ``` ## url The request url strings. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/about/me', async (c) => { const url = c.req.url // `http://localhost:8787/about/me` // ... }) ``` ## method The method name of the request. ```ts twoslash import { Hono } from 'hono' const app = new Hono() // ---cut--- app.get('/about/me', async (c) => { const method = c.req.method // `GET` // ... }) ``` ## raw The raw [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. ```ts // For Cloudflare Workers app.post('/', async (c) => { const metadata = c.req.raw.cf?.hostMetadata? // ... }) ``` ## cloneRawRequest() Clones the raw Request object from a HonoRequest. Works even after the request body has been consumed by validators or HonoRequest methods. ```ts twoslash import { Hono } from 'hono' const app = new Hono() import { cloneRawRequest } from 'hono/request' import { validator } from 'hono/validator' app.post( '/forward', validator('json', (data) => data), async (c) => { // Clone after validation const clonedReq = await cloneRawRequest(c.req) // Does not throw the error await clonedReq.json() // ... } ) ``` # 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) ``` # 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 { upgradeWebSocket, websocket } from 'hono/bun' // ... 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 { 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, } ``` # 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 } ``` # 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. # 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 # 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 `