├── .gitignore ├── .prettierrc ├── .vitepress ├── .gitignore ├── config.ts └── theme │ ├── custom.css │ └── index.ts ├── .vscode └── settings.json ├── README.md ├── docs ├── api │ ├── context.md │ ├── exception.md │ ├── hono.md │ ├── index.md │ ├── presets.md │ ├── request.md │ └── routing.md ├── concepts │ ├── benchmarks.md │ ├── developer-experience.md │ ├── middleware.md │ ├── motivation.md │ ├── routers.md │ ├── stacks.md │ └── web-standard.md ├── getting-started │ ├── ali-function-compute.md │ ├── aws-lambda.md │ ├── azure-functions.md │ ├── basic.md │ ├── bun.md │ ├── cloudflare-pages.md │ ├── cloudflare-workers.md │ ├── deno.md │ ├── fastly.md │ ├── google-cloud-run.md │ ├── lambda-edge.md │ ├── netlify.md │ ├── nodejs.md │ ├── service-worker.md │ ├── supabase-functions.md │ └── vercel.md ├── guides │ ├── best-practices.md │ ├── examples.md │ ├── faq.md │ ├── helpers.md │ ├── jsx-dom.md │ ├── jsx.md │ ├── middleware.md │ ├── others.md │ ├── rpc.md │ ├── testing.md │ └── validation.md ├── helpers │ ├── accepts.md │ ├── adapter.md │ ├── conninfo.md │ ├── cookie.md │ ├── css.md │ ├── dev.md │ ├── factory.md │ ├── html.md │ ├── jwt.md │ ├── proxy.md │ ├── ssg.md │ ├── streaming.md │ ├── testing.md │ └── websocket.md ├── index.md └── middleware │ ├── builtin │ ├── basic-auth.md │ ├── bearer-auth.md │ ├── body-limit.md │ ├── cache.md │ ├── combine.md │ ├── compress.md │ ├── context-storage.md │ ├── cors.md │ ├── csrf.md │ ├── etag.md │ ├── ip-restriction.md │ ├── jsx-renderer.md │ ├── jwk.md │ ├── jwt.md │ ├── language.md │ ├── logger.md │ ├── method-override.md │ ├── pretty-json.md │ ├── request-id.md │ ├── secure-headers.md │ ├── timeout.md │ ├── timing.md │ └── trailing-slash.md │ └── third-party.md ├── examples ├── better-auth-on-cloudflare.md ├── better-auth.md ├── cbor.md ├── cloudflare-durable-objects.md ├── cloudflare-queue.md ├── cloudflare-vitest.md ├── file-upload.md ├── grouping-routes-rpc.md ├── hono-openapi.md ├── htmx.md ├── index.md ├── menu.data.ts ├── prisma.md ├── proxy.md ├── pylon.md ├── stripe-webhook.md ├── swagger-ui.md ├── validator-error-handling.md ├── web-api.md ├── with-remix.md └── zod-openapi.md ├── index.md ├── package.json ├── public ├── _redirects ├── css │ └── syntax.css ├── favicon.ico └── images │ ├── bench01.png │ ├── bench02.png │ ├── bench03.png │ ├── bench04.png │ ├── bench05.png │ ├── bench06.png │ ├── bench07.png │ ├── bench08.png │ ├── bench09.png │ ├── bench10.png │ ├── bench11.png │ ├── bench12.png │ ├── bench13.png │ ├── bench14.png │ ├── bench15.png │ ├── bench16.png │ ├── code.png │ ├── code.webp │ ├── css-ss.png │ ├── hono-kawaii.png │ ├── hono-title.png │ ├── logo-large.png │ ├── logo.png │ ├── logo.svg │ ├── onion.png │ ├── pylon-example.png │ ├── router-linear.jpg │ ├── router-regexp.jpg │ ├── router-tree.jpg │ ├── sc.gif │ ├── sc01.gif │ ├── sc02.gif │ ├── sc03.gif │ ├── sc04.gif │ ├── ss.png │ ├── ss02.png │ ├── ss03.png │ └── timing-example.png └── scripts └── build-llm-docs.ts /.gitignore: -------------------------------------------------------------------------------- 1 | cache 2 | 3 | node_modules 4 | yarn-error.log 5 | pnpm-lock.yaml 6 | yarn.lock 7 | package-lock.json 8 | deno.lock 9 | bun.lockb 10 | 11 | # Windows 12 | Thumbs.db 13 | ehthumbs.db 14 | Desktop.ini 15 | $RECYCLE.BIN/ 16 | 17 | # OSX 18 | .DS_Store 19 | 20 | # Generated by Hugo 21 | .hugo_build.lock 22 | /resources 23 | .vercel 24 | 25 | # IDE-specific settings 26 | .idea 27 | 28 | # Docs for LLM 29 | public/llms.txt 30 | public/llms-full.txt 31 | public/llms-small.txt 32 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 70, 3 | "trailingComma": "es5", 4 | "tabWidth": 2, 5 | "semi": false, 6 | "singleQuote": true, 7 | "jsxSingleQuote": true, 8 | "endOfLine": "lf" 9 | } 10 | -------------------------------------------------------------------------------- /.vitepress/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | cache 3 | -------------------------------------------------------------------------------- /.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme' 2 | import type { EnhanceAppContext } from 'vitepress' 3 | import './custom.css' 4 | import 'virtual:group-icons.css' 5 | import '@shikijs/vitepress-twoslash/style.css' 6 | import ThoslashFloatingVue from '@shikijs/vitepress-twoslash/client' 7 | 8 | export default { 9 | extends: DefaultTheme, 10 | enhanceApp({ app }: EnhanceAppContext) { 11 | app.use(ThoslashFloatingVue) 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": false, 3 | "eslint.validate": [ 4 | "javascript", 5 | "javascriptreact", 6 | "typescript", 7 | "typescriptreact" 8 | ], 9 | "editor.codeActionsOnSave": { 10 | "source.fixAll.eslint": "explicit" 11 | } 12 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Repository for hono.dev 2 | 3 | This is a repository for the Hono website "_[hono.dev](https://hono.dev)_". 4 | 5 | ## Features 6 | 7 | - Built with [VitePress](https://vitepress.vuejs.org) 8 | - Deploy to Cloudflare Pages 9 | - Not using Hono :) 10 | 11 | ## Contribution 12 | 13 | Contribution welcome! 14 | You can help us by adding explanations, correcting grammar, or fixing a typo. 15 | 16 | ## License 17 | 18 | MIT 19 | 20 | ## Author 21 | 22 | Yusuke Wada 23 | -------------------------------------------------------------------------------- /docs/api/exception.md: -------------------------------------------------------------------------------- 1 | # Exception 2 | 3 | When a fatal error occurs, such as authentication failure, an HTTPException must be thrown. 4 | 5 | ## throw HTTPException 6 | 7 | This example throws an HTTPException from the middleware. 8 | 9 | ```ts twoslash 10 | import { Hono } from 'hono' 11 | const app = new Hono() 12 | declare const authorized: boolean 13 | // ---cut--- 14 | import { HTTPException } from 'hono/http-exception' 15 | 16 | // ... 17 | 18 | app.post('/auth', async (c, next) => { 19 | // authentication 20 | if (authorized === false) { 21 | throw new HTTPException(401, { message: 'Custom error message' }) 22 | } 23 | await next() 24 | }) 25 | ``` 26 | 27 | You can specify the response to be returned back to the user. 28 | 29 | ```ts twoslash 30 | import { HTTPException } from 'hono/http-exception' 31 | 32 | const errorResponse = new Response('Unauthorized', { 33 | status: 401, 34 | headers: { 35 | Authenticate: 'error="invalid_token"', 36 | }, 37 | }) 38 | 39 | throw new HTTPException(401, { res: errorResponse }) 40 | ``` 41 | 42 | ## Handling HTTPException 43 | 44 | You can handle the thrown HTTPException with `app.onError`. 45 | 46 | ```ts twoslash 47 | import { Hono } from 'hono' 48 | const app = new Hono() 49 | // ---cut--- 50 | import { HTTPException } from 'hono/http-exception' 51 | 52 | // ... 53 | 54 | app.onError((err, c) => { 55 | if (err instanceof HTTPException) { 56 | // Get the custom response 57 | return err.getResponse() 58 | } 59 | // ... 60 | // ---cut-start--- 61 | return c.text('Error') 62 | // ---cut-end--- 63 | }) 64 | ``` 65 | 66 | ## `cause` 67 | 68 | The `cause` option is available to add a [`cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) data. 69 | 70 | ```ts twoslash 71 | import { Hono, Context } from 'hono' 72 | import { HTTPException } from 'hono/http-exception' 73 | const app = new Hono() 74 | declare const message: string 75 | declare const authorize: (c: Context) => void 76 | // ---cut--- 77 | app.post('/auth', async (c, next) => { 78 | try { 79 | authorize(c) 80 | } catch (e) { 81 | throw new HTTPException(401, { message, cause: e }) 82 | } 83 | await next() 84 | }) 85 | ``` 86 | -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | Hono's API is simple. 4 | Just composed by extended objects from Web Standards. 5 | So, you can understand it well quickly. 6 | 7 | In this section, we introduce API of Hono like below. 8 | 9 | - Hono object 10 | - About routing 11 | - Context object 12 | - About middleware 13 | -------------------------------------------------------------------------------- /docs/api/presets.md: -------------------------------------------------------------------------------- 1 | # Presets 2 | 3 | Hono has several routers, each designed for a specific purpose. 4 | You can specify the router you want to use in the constructor of Hono. 5 | 6 | **Presets** are provided for common use cases, so you don't have to specify the router each time. 7 | The `Hono` class imported from all presets is the same, the only difference being the router. 8 | Therefore, you can use them interchangeably. 9 | 10 | ## `hono` 11 | 12 | Usage: 13 | 14 | ```ts twoslash 15 | import { Hono } from 'hono' 16 | ``` 17 | 18 | Routers: 19 | 20 | ```ts 21 | this.router = new SmartRouter({ 22 | routers: [new RegExpRouter(), new TrieRouter()], 23 | }) 24 | ``` 25 | 26 | ## `hono/quick` 27 | 28 | Usage: 29 | 30 | ```ts twoslash 31 | import { Hono } from 'hono/quick' 32 | ``` 33 | 34 | Router: 35 | 36 | ```ts 37 | this.router = new SmartRouter({ 38 | routers: [new LinearRouter(), new TrieRouter()], 39 | }) 40 | ``` 41 | 42 | ## `hono/tiny` 43 | 44 | Usage: 45 | 46 | ```ts twoslash 47 | import { Hono } from 'hono/tiny' 48 | ``` 49 | 50 | Router: 51 | 52 | ```ts 53 | this.router = new PatternRouter() 54 | ``` 55 | 56 | ## Which preset should I use? 57 | 58 | | Preset | Suitable platforms | 59 | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 60 | | `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**. 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. | 61 | | `hono/quick` | This preset is designed for environments where the application is initialized for every request. **Fastly Compute** operates in this manner, thus this preset is recommended for such use. | 62 | | `hono/tiny` | This is the smallest router package and it's suitable for environments where resources are limited. | 63 | -------------------------------------------------------------------------------- /docs/concepts/benchmarks.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | Benchmarks are only benchmarks, but they are important to us. 4 | 5 | ## Routers 6 | 7 | We measured the speeds of a bunch of JavaScript routers. 8 | For example, `find-my-way` is a very fast router used inside Fastify. 9 | 10 | - @medley/router 11 | - find-my-way 12 | - koa-tree-router 13 | - trek-router 14 | - express (includes handling) 15 | - koa-router 16 | 17 | First, we registered the following routing to each of our routers. 18 | These are similar to those used in the real world. 19 | 20 | ```ts twoslash 21 | interface Route { 22 | method: string 23 | path: string 24 | } 25 | // ---cut--- 26 | export const routes: Route[] = [ 27 | { method: 'GET', path: '/user' }, 28 | { method: 'GET', path: '/user/comments' }, 29 | { method: 'GET', path: '/user/avatar' }, 30 | { method: 'GET', path: '/user/lookup/username/:username' }, 31 | { method: 'GET', path: '/user/lookup/email/:address' }, 32 | { method: 'GET', path: '/event/:id' }, 33 | { method: 'GET', path: '/event/:id/comments' }, 34 | { method: 'POST', path: '/event/:id/comment' }, 35 | { method: 'GET', path: '/map/:location/events' }, 36 | { method: 'GET', path: '/status' }, 37 | { method: 'GET', path: '/very/deeply/nested/route/hello/there' }, 38 | { method: 'GET', path: '/static/*' }, 39 | ] 40 | ``` 41 | 42 | Then we sent the Request to the endpoints like below. 43 | 44 | ```ts twoslash 45 | interface Route { 46 | method: string 47 | path: string 48 | } 49 | // ---cut--- 50 | const routes: (Route & { name: string })[] = [ 51 | { 52 | name: 'short static', 53 | method: 'GET', 54 | path: '/user', 55 | }, 56 | { 57 | name: 'static with same radix', 58 | method: 'GET', 59 | path: '/user/comments', 60 | }, 61 | { 62 | name: 'dynamic route', 63 | method: 'GET', 64 | path: '/user/lookup/username/hey', 65 | }, 66 | { 67 | name: 'mixed static dynamic', 68 | method: 'GET', 69 | path: '/event/abcd1234/comments', 70 | }, 71 | { 72 | name: 'post', 73 | method: 'POST', 74 | path: '/event/abcd1234/comment', 75 | }, 76 | { 77 | name: 'long static', 78 | method: 'GET', 79 | path: '/very/deeply/nested/route/hello/there', 80 | }, 81 | { 82 | name: 'wildcard', 83 | method: 'GET', 84 | path: '/static/index.html', 85 | }, 86 | ] 87 | ``` 88 | 89 | Let's see the results. 90 | 91 | ### On Node.js 92 | 93 | The following screenshots show the results on Node.js. 94 | 95 | ![](/images/bench01.png) 96 | 97 | ![](/images/bench02.png) 98 | 99 | ![](/images/bench03.png) 100 | 101 | ![](/images/bench04.png) 102 | 103 | ![](/images/bench05.png) 104 | 105 | ![](/images/bench06.png) 106 | 107 | ![](/images/bench07.png) 108 | 109 | ![](/images/bench08.png) 110 | 111 | ### On Bun 112 | 113 | The following screenshots show the results on Bun. 114 | 115 | ![](/images/bench09.png) 116 | 117 | ![](/images/bench10.png) 118 | 119 | ![](/images/bench11.png) 120 | 121 | ![](/images/bench12.png) 122 | 123 | ![](/images/bench13.png) 124 | 125 | ![](/images/bench14.png) 126 | 127 | ![](/images/bench15.png) 128 | 129 | ![](/images/bench16.png) 130 | 131 | ## Cloudflare Workers 132 | 133 | **Hono is the fastest**, compared to other routers for Cloudflare Workers. 134 | 135 | - Machine: Apple MacBook Pro, 32 GiB, M1 Pro 136 | - Scripts: [benchmarks/handle-event](https://github.com/honojs/hono/tree/main/benchmarks/handle-event) 137 | 138 | ``` 139 | Hono x 402,820 ops/sec ±4.78% (80 runs sampled) 140 | itty-router x 212,598 ops/sec ±3.11% (87 runs sampled) 141 | sunder x 297,036 ops/sec ±4.76% (77 runs sampled) 142 | worktop x 197,345 ops/sec ±2.40% (88 runs sampled) 143 | Fastest is Hono 144 | ✨ Done in 28.06s. 145 | ``` 146 | 147 | ## Deno 148 | 149 | **Hono is the fastest**, compared to other frameworks for Deno. 150 | 151 | - Machine: Apple MacBook Pro, 32 GiB, M1 Pro, Deno v1.22.0 152 | - Scripts: [benchmarks/deno](https://github.com/honojs/hono/tree/main/benchmarks/deno) 153 | - Method: `bombardier --fasthttp -d 10s -c 100 'http://localhost:8000/user/lookup/username/foo'` 154 | 155 | | Framework | Version | Results | 156 | | --------- | :----------: | -----------------------: | 157 | | **Hono** | 3.0.0 | **Requests/sec: 136112** | 158 | | Fast | 4.0.0-beta.1 | Requests/sec: 103214 | 159 | | Megalo | 0.3.0 | Requests/sec: 64597 | 160 | | Faster | 5.7 | Requests/sec: 54801 | 161 | | oak | 10.5.1 | Requests/sec: 43326 | 162 | | opine | 2.2.0 | Requests/sec: 30700 | 163 | 164 | Another benchmark result: [denosaurs/bench](https://github.com/denosaurs/bench) 165 | 166 | ## Bun 167 | 168 | Hono is one of the fastest frameworks for Bun. 169 | You can see it below. 170 | 171 | - [SaltyAom/bun-http-framework-benchmark](https://github.com/SaltyAom/bun-http-framework-benchmark) 172 | -------------------------------------------------------------------------------- /docs/concepts/developer-experience.md: -------------------------------------------------------------------------------- 1 | # Developer Experience 2 | 3 | To create a great application, we need great development experience. 4 | Fortunately, we can write applications for Cloudflare Workers, Deno, and Bun in TypeScript without having the need to transpile it to JavaScript. 5 | Hono is written in TypeScript and can make applications type-safe. 6 | -------------------------------------------------------------------------------- /docs/concepts/middleware.md: -------------------------------------------------------------------------------- 1 | # Middleware 2 | 3 | We call the primitive that returns `Response` as "Handler". 4 | "Middleware" is executed before and after the Handler and handles the `Request` and `Response`. 5 | It's like an onion structure. 6 | 7 | ![](/images/onion.png) 8 | 9 | For example, we can write the middleware to add the "X-Response-Time" header as follows. 10 | 11 | ```ts twoslash 12 | import { Hono } from 'hono' 13 | const app = new Hono() 14 | // ---cut--- 15 | app.use(async (c, next) => { 16 | const start = Date.now() 17 | await next() 18 | const end = Date.now() 19 | c.res.headers.set('X-Response-Time', `${end - start}`) 20 | }) 21 | ``` 22 | 23 | With this simple method, we can write our own custom middleware and we can use the built-in or third party middleware. 24 | -------------------------------------------------------------------------------- /docs/concepts/motivation.md: -------------------------------------------------------------------------------- 1 | # Philosophy 2 | 3 | In this section, we talk about the concept, or philosophy, of Hono. 4 | 5 | ## Motivation 6 | 7 | At first, I just wanted to create a web application on Cloudflare Workers. 8 | But, there was no good framework that works on Cloudflare Workers. 9 | So, I started building Hono. 10 | 11 | I thought it would be a good opportunity to learn how to build a router using Trie trees. 12 | Then a friend showed up with ultra crazy fast router called "RegExpRouter". 13 | And I also have a friend who created the Basic authentication middleware. 14 | 15 | 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". 16 | (Although Express works on Bun now.) 17 | 18 | We also have friends who make GraphQL servers, Firebase authentication, and Sentry middleware. 19 | And, we also have a Node.js adapter. 20 | An ecosystem has sprung up. 21 | 22 | In other words, Hono is damn fast, makes a lot of things possible, and works anywhere. 23 | We might imagine that Hono could become the **Standard for Web Standards**. 24 | -------------------------------------------------------------------------------- /docs/concepts/routers.md: -------------------------------------------------------------------------------- 1 | # Routers 2 | 3 | The routers are the most important features for Hono. 4 | 5 | Hono has five routers. 6 | 7 | ## RegExpRouter 8 | 9 | **RegExpRouter** is the fastest router in the JavaScript world. 10 | 11 | Although this is called "RegExp" it is not an Express-like implementation using [path-to-regexp](https://github.com/pillarjs/path-to-regexp). 12 | They are using linear loops. 13 | Therefore, regular expression matching will be performed for all routes and the performance will be degraded as you have more routes. 14 | 15 | ![](/images/router-linear.jpg) 16 | 17 | Hono's RegExpRouter turns the route pattern into "one large regular expression". 18 | Then it can get the result with one-time matching. 19 | 20 | ![](/images/router-regexp.jpg) 21 | 22 | This works faster than methods that use tree-based algorithms such as radix-tree in most cases. 23 | 24 | ## TrieRouter 25 | 26 | **TrieRouter** is the router using the Trie-tree algorithm. 27 | It does not use linear loops as same as RegExpRouter. 28 | 29 | ![](/images/router-tree.jpg) 30 | 31 | This router is not as fast as the RegExpRouter, but it is much faster than the Express router. 32 | TrieRouter supports all patterns though RegExpRouter does not. 33 | 34 | ## SmartRouter 35 | 36 | RegExpRouter doesn't support all routing patterns. 37 | Therefore, it's usually used in combination with another router that does support all patterns. 38 | 39 | **SmartRouter** will select the best router by inferring from the registered routers. 40 | Hono uses SmartRouter and the two routers by default: 41 | 42 | ```ts 43 | // Inside the core of Hono. 44 | readonly defaultRouter: Router = new SmartRouter({ 45 | routers: [new RegExpRouter(), new TrieRouter()], 46 | }) 47 | ``` 48 | 49 | When the application starts, SmartRouter detects the fastest router based on routing and continues to use it. 50 | 51 | ## LinearRouter 52 | 53 | RegExpRouter is fast, but the route registration phase can be slightly slow. 54 | So, it's not suitable for an environment that initializes with every request. 55 | 56 | **LinearRouter** is optimized for "one shot" situations. 57 | Route registration is significantly faster than with RegExpRouter because it adds the route without compiling strings, using a linear approach. 58 | 59 | The following is one of the benchmark results, which includes the route registration phase. 60 | 61 | ```console 62 | • GET /user/lookup/username/hey 63 | ----------------------------------------------------- ----------------------------- 64 | LinearRouter 1.82 µs/iter (1.7 µs … 2.04 µs) 1.84 µs 2.04 µs 2.04 µs 65 | MedleyRouter 4.44 µs/iter (4.34 µs … 4.54 µs) 4.48 µs 4.54 µs 4.54 µs 66 | FindMyWay 60.36 µs/iter (45.5 µs … 1.9 ms) 59.88 µs 78.13 µs 82.92 µs 67 | KoaTreeRouter 3.81 µs/iter (3.73 µs … 3.87 µs) 3.84 µs 3.87 µs 3.87 µs 68 | TrekRouter 5.84 µs/iter (5.75 µs … 6.04 µs) 5.86 µs 6.04 µs 6.04 µs 69 | 70 | summary for GET /user/lookup/username/hey 71 | LinearRouter 72 | 2.1x faster than KoaTreeRouter 73 | 2.45x faster than MedleyRouter 74 | 3.21x faster than TrekRouter 75 | 33.24x faster than FindMyWay 76 | ``` 77 | 78 | For situations like Fastly Compute, it's better to use LinearRouter with the `hono/quick` preset. 79 | 80 | ## PatternRouter 81 | 82 | **PatternRouter** is the smallest router among Hono's routers. 83 | 84 | While Hono is already compact, if you need to make it even smaller for an environment with limited resources, you can use PatternRouter. 85 | 86 | An application using only PatternRouter is under 15KB in size. 87 | 88 | ```console 89 | $ npx wrangler deploy --minify ./src/index.ts 90 | ⛅️ wrangler 3.20.0 91 | ------------------- 92 | Total Upload: 14.68 KiB / gzip: 5.38 KiB 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/concepts/web-standard.md: -------------------------------------------------------------------------------- 1 | # Web Standards 2 | 3 | Hono uses only **Web Standards** like Fetch. 4 | They were originally used in the `fetch` function and consist of basic objects that handle HTTP requests and responses. 5 | In addition to `Requests` and `Responses`, there are `URL`, `URLSearchParam`, `Headers` and others. 6 | 7 | Cloudflare Workers, Deno, and Bun also build upon Web Standards. 8 | For example, a server that returns "Hello World" could be written as below. This could run on Cloudflare Workers and Bun. 9 | 10 | ```ts twoslash 11 | export default { 12 | async fetch() { 13 | return new Response('Hello World') 14 | }, 15 | } 16 | ``` 17 | 18 | Hono uses only Web Standards, which means that Hono can run on any runtime that supports them. 19 | In addition, we have a Node.js adapter. Hono runs on these runtimes: 20 | 21 | - Cloudflare Workers (`workerd`) 22 | - Deno 23 | - Bun 24 | - Fastly Compute 25 | - AWS Lambda 26 | - Node.js 27 | - Vercel (edge-light) 28 | 29 | It also works on Netlify and other platforms. 30 | The same code runs on all platforms. 31 | 32 | Cloudflare Workers, Deno, Shopify, and others launched [WinterCG](https://wintercg.org) to discuss the possibility of using the Web Standards to enable "web-interoperability". 33 | Hono will follow their steps and go for **the Standard of the Web Standards**. 34 | -------------------------------------------------------------------------------- /docs/getting-started/ali-function-compute.md: -------------------------------------------------------------------------------- 1 | # Alibaba Cloud Function Compute 2 | 3 | [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. 4 | 5 | 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. 6 | 7 | ## 1. Setup 8 | 9 | ::: code-group 10 | 11 | ```sh [npm] 12 | mkdir my-app 13 | cd my-app 14 | npm i hono hono-alibaba-cloud-fc3-adapter 15 | npm i -D @serverless-devs/s esbuild 16 | mkdir src 17 | touch src/index.ts 18 | ``` 19 | 20 | ```sh [yarn] 21 | mkdir my-app 22 | cd my-app 23 | yarn add hono hono-alibaba-cloud-fc3-adapter 24 | yarn add -D @serverless-devs/s esbuild 25 | mkdir src 26 | touch src/index.ts 27 | ``` 28 | 29 | ```sh [pnpm] 30 | mkdir my-app 31 | cd my-app 32 | pnpm add hono hono-alibaba-cloud-fc3-adapter 33 | pnpm add -D @serverless-devs/s esbuild 34 | mkdir src 35 | touch src/index.ts 36 | ``` 37 | 38 | ```sh [bun] 39 | mkdir my-app 40 | cd my-app 41 | bun add hono hono-alibaba-cloud-fc3-adapter 42 | bun add -D esbuild @serverless-devs/s 43 | mkdir src 44 | touch src/index.ts 45 | ``` 46 | 47 | ::: 48 | 49 | ## 2. Hello World 50 | 51 | Edit `src/index.ts`. 52 | 53 | ```ts 54 | import { Hono } from 'hono' 55 | import { handle } from 'hono-alibaba-cloud-fc3-adapter' 56 | 57 | const app = new Hono() 58 | 59 | app.get('/', (c) => c.text('Hello Hono!')) 60 | 61 | export const handler = handle(app) 62 | ``` 63 | 64 | ## 3. Setup serverless-devs 65 | 66 | > [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. 67 | 68 | Add the Alibaba Cloud AccessKeyID & AccessKeySecret 69 | 70 | ```sh 71 | npx s config add 72 | # Please select a provider: Alibaba Cloud (alibaba) 73 | # Input your AccessKeyID & AccessKeySecret 74 | ``` 75 | 76 | Edit `s.yaml` 77 | 78 | ```yaml 79 | edition: 3.0.0 80 | name: my-app 81 | access: 'default' 82 | 83 | vars: 84 | region: 'us-west-1' 85 | 86 | resources: 87 | my-app: 88 | component: fc3 89 | props: 90 | region: ${vars.region} 91 | functionName: 'my-app' 92 | description: 'Hello World by Hono' 93 | runtime: 'nodejs20' 94 | code: ./dist 95 | handler: index.handler 96 | memorySize: 1024 97 | timeout: 300 98 | ``` 99 | 100 | Edit `scripts` section in `package.json`: 101 | 102 | ```json 103 | { 104 | "scripts": { 105 | "build": "esbuild --bundle --outfile=./dist/index.js --platform=node --target=node20 ./src/index.ts", 106 | "deploy": "s deploy -y" 107 | } 108 | } 109 | ``` 110 | 111 | ## 4. Deploy 112 | 113 | Finally, run the command to deploy: 114 | 115 | ```sh 116 | npm run build # Compile the TypeScript code to JavaScript 117 | npm run deploy # Deploy the function to Alibaba Cloud Function Compute 118 | ``` 119 | -------------------------------------------------------------------------------- /docs/getting-started/azure-functions.md: -------------------------------------------------------------------------------- 1 | # Azure Functions 2 | 3 | [Azure Functions](https://azure.microsoft.com/en-us/products/functions) is a serverless platform from Microsoft Azure. You can run your code in response to events, and it automatically manages the underlying compute resources for you. 4 | 5 | Hono was not designed for Azure Functions at first. But with [Azure Functions Adapter](https://github.com/Marplex/hono-azurefunc-adapter) it can run on it as well. 6 | 7 | It works with Azure Functions **V4** running on Node.js 18 or above. 8 | 9 | ## 1. Install CLI 10 | 11 | To create an Azure Function, you must first install [Azure Functions Core Tools](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-typescript?pivots=nodejs-model-v4#install-the-azure-functions-core-tools). 12 | 13 | On macOS 14 | 15 | ```sh 16 | brew tap azure/functions 17 | brew install azure-functions-core-tools@4 18 | ``` 19 | 20 | Follow this link for other OS: 21 | 22 | - [Install the Azure Functions Core Tools | Microsoft Learn](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-typescript?pivots=nodejs-model-v4#install-the-azure-functions-core-tools) 23 | 24 | ## 2. Setup 25 | 26 | Create a TypeScript Node.js V4 project in the current folder. 27 | 28 | ```sh 29 | func init --typescript 30 | ``` 31 | 32 | Change the default route prefix of the host. Add this property to the root json object of `host.json`: 33 | 34 | ```json 35 | "extensions": { 36 | "http": { 37 | "routePrefix": "" 38 | } 39 | } 40 | ``` 41 | 42 | ::: info 43 | The default Azure Functions route prefix is `/api`. If you don't change it as shown above, be sure to start all your Hono routes with `/api` 44 | ::: 45 | 46 | Now you are ready to install Hono and the Azure Functions Adapter with: 47 | 48 | ::: code-group 49 | 50 | ```sh [npm] 51 | npm i @marplex/hono-azurefunc-adapter hono 52 | ``` 53 | 54 | ```sh [yarn] 55 | yarn add @marplex/hono-azurefunc-adapter hono 56 | ``` 57 | 58 | ```sh [pnpm] 59 | pnpm add @marplex/hono-azurefunc-adapter hono 60 | ``` 61 | 62 | ```sh [bun] 63 | bun add @marplex/hono-azurefunc-adapter hono 64 | ``` 65 | 66 | ::: 67 | 68 | ## 3. Hello World 69 | 70 | Create `src/app.ts`: 71 | 72 | ```ts 73 | // src/app.ts 74 | import { Hono } from 'hono' 75 | const app = new Hono() 76 | 77 | app.get('/', (c) => c.text('Hello Azure Functions!')) 78 | 79 | export default app 80 | ``` 81 | 82 | Create `src/functions/httpTrigger.ts`: 83 | 84 | ```ts 85 | // src/functions/httpTrigger.ts 86 | import { app } from '@azure/functions' 87 | import { azureHonoHandler } from '@marplex/hono-azurefunc-adapter' 88 | import honoApp from '../app' 89 | 90 | app.http('httpTrigger', { 91 | methods: [ 92 | //Add all your supported HTTP methods here 93 | 'GET', 94 | 'POST', 95 | 'DELETE', 96 | 'PUT', 97 | ], 98 | authLevel: 'anonymous', 99 | route: '{*proxy}', 100 | handler: azureHonoHandler(honoApp.fetch), 101 | }) 102 | ``` 103 | 104 | ## 4. Run 105 | 106 | Run the development server locally. Then, access `http://localhost:7071` in your Web browser. 107 | 108 | ::: code-group 109 | 110 | ```sh [npm] 111 | npm run start 112 | ``` 113 | 114 | ```sh [yarn] 115 | yarn start 116 | ``` 117 | 118 | ```sh [pnpm] 119 | pnpm start 120 | ``` 121 | 122 | ```sh [bun] 123 | bun run start 124 | ``` 125 | 126 | ::: 127 | 128 | ## 5. Deploy 129 | 130 | ::: info 131 | Before you can deploy to Azure, you need to create some resources in your cloud infrastructure. Please visit the Microsoft documentation on [Create supporting Azure resources for your function](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-typescript?pivots=nodejs-model-v4&tabs=windows%2Cazure-cli%2Cbrowser#create-supporting-azure-resources-for-your-function) 132 | ::: 133 | 134 | Build the project for deployment: 135 | 136 | ::: code-group 137 | 138 | ```sh [npm] 139 | npm run build 140 | ``` 141 | 142 | ```sh [yarn] 143 | yarn build 144 | ``` 145 | 146 | ```sh [pnpm] 147 | pnpm build 148 | ``` 149 | 150 | ```sh [bun] 151 | bun run build 152 | ``` 153 | 154 | ::: 155 | 156 | Deploy your project to the function app in Azure Cloud. Replace `` with the name of your app. 157 | 158 | ```sh 159 | func azure functionapp publish 160 | ``` 161 | -------------------------------------------------------------------------------- /docs/getting-started/bun.md: -------------------------------------------------------------------------------- 1 | # Bun 2 | 3 | [Bun](https://bun.sh) is another JavaScript runtime. It's not Node.js or Deno. Bun includes a trans compiler, we can write the code with TypeScript. 4 | Hono also works on Bun. 5 | 6 | ## 1. Install Bun 7 | 8 | To install `bun` command, follow the instruction in [the official web site](https://bun.sh). 9 | 10 | ## 2. Setup 11 | 12 | ### 2.1. Setup a new project 13 | 14 | A starter for Bun is available. Start your project with "bun create" command. 15 | Select `bun` template for this example. 16 | 17 | ```sh 18 | bun create hono@latest my-app 19 | ``` 20 | 21 | Move into my-app and install the dependencies. 22 | 23 | ```sh 24 | cd my-app 25 | bun install 26 | ``` 27 | 28 | ### 2.2. Setup an existing project 29 | 30 | On an existing Bun project, we only need to install `hono` dependencies on the project root directory via 31 | 32 | ```sh 33 | bun add hono 34 | ``` 35 | 36 | ## 3. Hello World 37 | 38 | "Hello World" script is below. Almost the same as writing on other platforms. 39 | 40 | ```ts 41 | import { Hono } from 'hono' 42 | 43 | const app = new Hono() 44 | app.get('/', (c) => c.text('Hello Bun!')) 45 | 46 | export default app 47 | ``` 48 | 49 | ## 4. Run 50 | 51 | Run the command. 52 | 53 | ```sh 54 | bun run dev 55 | ``` 56 | 57 | Then, access `http://localhost:3000` in your browser. 58 | 59 | ## Change port number 60 | 61 | You can specify the port number with exporting the `port`. 62 | 63 | 64 | ```ts 65 | import { Hono } from 'hono' 66 | 67 | const app = new Hono() 68 | app.get('/', (c) => c.text('Hello Bun!')) 69 | 70 | export default app // [!code --] 71 | export default { // [!code ++] 72 | port: 3000, // [!code ++] 73 | fetch: app.fetch, // [!code ++] 74 | } // [!code ++] 75 | ``` 76 | 77 | ## Serve static files 78 | 79 | To serve static files, use `serveStatic` imported from `hono/bun`. 80 | 81 | ```ts 82 | import { serveStatic } from 'hono/bun' 83 | 84 | const app = new Hono() 85 | 86 | app.use('/static/*', serveStatic({ root: './' })) 87 | app.use('/favicon.ico', serveStatic({ path: './favicon.ico' })) 88 | app.get('/', (c) => c.text('You can access: /static/hello.txt')) 89 | app.get('*', serveStatic({ path: './static/fallback.txt' })) 90 | ``` 91 | 92 | For the above code, it will work well with the following directory structure. 93 | 94 | ``` 95 | ./ 96 | ├── favicon.ico 97 | ├── src 98 | └── static 99 | ├── demo 100 | │ └── index.html 101 | ├── fallback.txt 102 | ├── hello.txt 103 | └── images 104 | └── dinotocat.png 105 | ``` 106 | 107 | ### `rewriteRequestPath` 108 | 109 | If you want to map `http://localhost:3000/static/*` to `./statics`, you can use the `rewriteRequestPath` option: 110 | 111 | ```ts 112 | app.get( 113 | '/static/*', 114 | serveStatic({ 115 | root: './', 116 | rewriteRequestPath: (path) => 117 | path.replace(/^\/static/, '/statics'), 118 | }) 119 | ) 120 | ``` 121 | 122 | ### `mimes` 123 | 124 | You can add MIME types with `mimes`: 125 | 126 | ```ts 127 | app.get( 128 | '/static/*', 129 | serveStatic({ 130 | mimes: { 131 | m3u8: 'application/vnd.apple.mpegurl', 132 | ts: 'video/mp2t', 133 | }, 134 | }) 135 | ) 136 | ``` 137 | 138 | ### `onFound` 139 | 140 | You can specify handling when the requested file is found with `onFound`: 141 | 142 | ```ts 143 | app.get( 144 | '/static/*', 145 | serveStatic({ 146 | // ... 147 | onFound: (_path, c) => { 148 | c.header('Cache-Control', `public, immutable, max-age=31536000`) 149 | }, 150 | }) 151 | ) 152 | ``` 153 | 154 | ### `onNotFound` 155 | 156 | You can specify handling when the requested file is not found with `onNotFound`: 157 | 158 | ```ts 159 | app.get( 160 | '/static/*', 161 | serveStatic({ 162 | onNotFound: (path, c) => { 163 | console.log(`${path} is not found, you access ${c.req.path}`) 164 | }, 165 | }) 166 | ) 167 | ``` 168 | 169 | ### `precompressed` 170 | 171 | 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. 172 | 173 | ```ts 174 | app.get( 175 | '/static/*', 176 | serveStatic({ 177 | precompressed: true, 178 | }) 179 | ) 180 | ``` 181 | 182 | ## Testing 183 | 184 | You can use `bun:test` for testing on Bun. 185 | 186 | ```ts 187 | import { describe, expect, it } from 'bun:test' 188 | import app from '.' 189 | 190 | describe('My first test', () => { 191 | it('Should return 200 Response', async () => { 192 | const req = new Request('http://localhost/') 193 | const res = await app.fetch(req) 194 | expect(res.status).toBe(200) 195 | }) 196 | }) 197 | ``` 198 | 199 | Then, run the command. 200 | 201 | ```sh 202 | bun test index.test.ts 203 | ``` 204 | -------------------------------------------------------------------------------- /docs/getting-started/deno.md: -------------------------------------------------------------------------------- 1 | # Deno 2 | 3 | [Deno](https://deno.com/) is a JavaScript runtime built on V8. It's not Node.js. 4 | Hono also works on Deno. 5 | 6 | You can use Hono, write the code with TypeScript, run the application with the `deno` command, and deploy it to "Deno Deploy". 7 | 8 | ## 1. Install Deno 9 | 10 | First, install `deno` command. 11 | Please refer to [the official document](https://docs.deno.com/runtime/manual/getting_started/installation). 12 | 13 | ## 2. Setup 14 | 15 | A starter for Deno is available. 16 | Start your project with "create-hono" command. 17 | 18 | ```sh 19 | deno init --npm hono my-app 20 | ``` 21 | 22 | Select `deno` template for this example. 23 | 24 | Move into `my-app`. For Deno, you don't have to install Hono explicitly. 25 | 26 | ```sh 27 | cd my-app 28 | ``` 29 | 30 | ## 3. Hello World 31 | 32 | Write your first application. 33 | 34 | ```ts 35 | import { Hono } from 'hono' 36 | 37 | const app = new Hono() 38 | 39 | app.get('/', (c) => c.text('Hello Deno!')) 40 | 41 | Deno.serve(app.fetch) 42 | ``` 43 | 44 | ## 4. Run 45 | 46 | Just this command: 47 | 48 | ```sh 49 | deno task start 50 | ``` 51 | 52 | ## Change port number 53 | 54 | You can specify the port number by updating the arguments of `Deno.serve` in `main.ts`: 55 | 56 | ```ts 57 | Deno.serve(app.fetch) // [!code --] 58 | Deno.serve({ port: 8787 }, app.fetch) // [!code ++] 59 | ``` 60 | 61 | ## Serve static files 62 | 63 | To serve static files, use `serveStatic` imported from `hono/middleware.ts`. 64 | 65 | ```ts 66 | import { Hono } from 'hono' 67 | import { serveStatic } from 'hono/deno' 68 | 69 | const app = new Hono() 70 | 71 | app.use('/static/*', serveStatic({ root: './' })) 72 | app.use('/favicon.ico', serveStatic({ path: './favicon.ico' })) 73 | app.get('/', (c) => c.text('You can access: /static/hello.txt')) 74 | app.get('*', serveStatic({ path: './static/fallback.txt' })) 75 | 76 | Deno.serve(app.fetch) 77 | ``` 78 | 79 | For the above code, it will work well with the following directory structure. 80 | 81 | ``` 82 | ./ 83 | ├── favicon.ico 84 | ├── index.ts 85 | └── static 86 | ├── demo 87 | │ └── index.html 88 | ├── fallback.txt 89 | ├── hello.txt 90 | └── images 91 | └── dinotocat.png 92 | ``` 93 | 94 | ### `rewriteRequestPath` 95 | 96 | If you want to map `http://localhost:8000/static/*` to `./statics`, you can use the `rewriteRequestPath` option: 97 | 98 | ```ts 99 | app.get( 100 | '/static/*', 101 | serveStatic({ 102 | root: './', 103 | rewriteRequestPath: (path) => 104 | path.replace(/^\/static/, '/statics'), 105 | }) 106 | ) 107 | ``` 108 | 109 | ### `mimes` 110 | 111 | You can add MIME types with `mimes`: 112 | 113 | ```ts 114 | app.get( 115 | '/static/*', 116 | serveStatic({ 117 | mimes: { 118 | m3u8: 'application/vnd.apple.mpegurl', 119 | ts: 'video/mp2t', 120 | }, 121 | }) 122 | ) 123 | ``` 124 | 125 | ### `onFound` 126 | 127 | You can specify handling when the requested file is found with `onFound`: 128 | 129 | ```ts 130 | app.get( 131 | '/static/*', 132 | serveStatic({ 133 | // ... 134 | onFound: (_path, c) => { 135 | c.header('Cache-Control', `public, immutable, max-age=31536000`) 136 | }, 137 | }) 138 | ) 139 | ``` 140 | 141 | ### `onNotFound` 142 | 143 | You can specify handling when the requested file is not found with `onNotFound`: 144 | 145 | ```ts 146 | app.get( 147 | '/static/*', 148 | serveStatic({ 149 | onNotFound: (path, c) => { 150 | console.log(`${path} is not found, you access ${c.req.path}`) 151 | }, 152 | }) 153 | ) 154 | ``` 155 | 156 | ### `precompressed` 157 | 158 | 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. 159 | 160 | ```ts 161 | app.get( 162 | '/static/*', 163 | serveStatic({ 164 | precompressed: true, 165 | }) 166 | ) 167 | ``` 168 | 169 | ## Deno Deploy 170 | 171 | Deno Deploy is an edge runtime platform for Deno. 172 | We can publish the application world widely on Deno Deploy. 173 | 174 | Hono also supports Deno Deploy. Please refer to [the official document](https://docs.deno.com/deploy/manual/). 175 | 176 | ## Testing 177 | 178 | Testing the application on Deno is easy. 179 | You can write with `Deno.test` and use `assert` or `assertEquals` from [@std/assert](https://jsr.io/@std/assert). 180 | 181 | ```sh 182 | deno add jsr:@std/assert 183 | ``` 184 | 185 | ```ts 186 | import { Hono } from 'hono' 187 | import { assertEquals } from '@std/assert' 188 | 189 | Deno.test('Hello World', async () => { 190 | const app = new Hono() 191 | app.get('/', (c) => c.text('Please test me')) 192 | 193 | const res = await app.request('http://localhost/') 194 | assertEquals(res.status, 200) 195 | }) 196 | ``` 197 | 198 | Then run the command: 199 | 200 | ```sh 201 | deno test hello.ts 202 | ``` 203 | 204 | ## `npm:` specifier 205 | 206 | `npm:hono` is also available. You can use it by fixing the `deno.json`: 207 | 208 | ```json 209 | { 210 | "imports": { 211 | "hono": "jsr:@hono/hono" // [!code --] 212 | "hono": "npm:hono" // [!code ++] 213 | } 214 | } 215 | ``` 216 | 217 | You can use either `npm:hono` or `jsr:@hono/hono`. 218 | 219 | If you want to use Third-party Middleware such as `npm:@hono/zod-validator` with the TypeScript Type inferences, you need to use the `npm:` specifier. 220 | 221 | ```json 222 | { 223 | "imports": { 224 | "hono": "npm:hono", 225 | "zod": "npm:zod", 226 | "@hono/zod-validator": "npm:@hono/zod-validator" 227 | } 228 | } 229 | ``` 230 | -------------------------------------------------------------------------------- /docs/getting-started/fastly.md: -------------------------------------------------------------------------------- 1 | # Fastly Compute 2 | 3 | [Fastly Compute](https://www.fastly.com/products/edge-compute) is an advanced edge computing system that runs your code, in your favorite language, on our global edge network. Hono also works on Fastly Compute. 4 | 5 | You can develop the application locally and publish it with a few commands using [Fastly CLI](https://www.fastly.com/documentation/reference/tools/cli/). 6 | 7 | ## 1. Setup 8 | 9 | A starter for Fastly Compute is available. 10 | Start your project with "create-hono" command. 11 | Select `fastly` template for this example. 12 | 13 | ::: code-group 14 | 15 | ```sh [npm] 16 | npm create hono@latest my-app 17 | ``` 18 | 19 | ```sh [yarn] 20 | yarn create hono my-app 21 | ``` 22 | 23 | ```sh [pnpm] 24 | pnpm create hono my-app 25 | ``` 26 | 27 | ```sh [bun] 28 | bun create hono@latest my-app 29 | ``` 30 | 31 | ```sh [deno] 32 | deno init --npm hono my-app 33 | ``` 34 | 35 | ::: 36 | 37 | Move to `my-app` and install the dependencies. 38 | 39 | ::: code-group 40 | 41 | ```sh [npm] 42 | cd my-app 43 | npm i 44 | ``` 45 | 46 | ```sh [yarn] 47 | cd my-app 48 | yarn 49 | ``` 50 | 51 | ```sh [pnpm] 52 | cd my-app 53 | pnpm i 54 | ``` 55 | 56 | ```sh [bun] 57 | cd my-app 58 | bun i 59 | ``` 60 | 61 | ::: 62 | 63 | ## 2. Hello World 64 | 65 | Edit `src/index.ts`: 66 | 67 | ```ts 68 | // src/index.ts 69 | import { Hono } from 'hono' 70 | const app = new Hono() 71 | 72 | app.get('/', (c) => c.text('Hello Fastly!')) 73 | 74 | app.fire() 75 | ``` 76 | 77 | ## 3. Run 78 | 79 | Run the development server locally. Then, access `http://localhost:7676` in your Web browser. 80 | 81 | ::: code-group 82 | 83 | ```sh [npm] 84 | npm run start 85 | ``` 86 | 87 | ```sh [yarn] 88 | yarn start 89 | ``` 90 | 91 | ```sh [pnpm] 92 | pnpm run start 93 | ``` 94 | 95 | ```sh [bun] 96 | bun run start 97 | ``` 98 | 99 | ::: 100 | 101 | ## 4. Deploy 102 | 103 | 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. 104 | 105 | If you don't have an account yet, you must [create your Fastly account](https://www.fastly.com/signup/). 106 | 107 | ::: code-group 108 | 109 | ```sh [npm] 110 | npm run deploy 111 | ``` 112 | 113 | ```sh [yarn] 114 | yarn deploy 115 | ``` 116 | 117 | ```sh [pnpm] 118 | pnpm run deploy 119 | ``` 120 | 121 | ```sh [bun] 122 | bun run deploy 123 | ``` 124 | 125 | ::: 126 | -------------------------------------------------------------------------------- /docs/getting-started/google-cloud-run.md: -------------------------------------------------------------------------------- 1 | # Google Cloud Run 2 | 3 | [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. 4 | 5 | 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 Nodejs buildpack. 6 | 7 | This guide assumes you already have a Google Cloud account and a billing account. 8 | 9 | ## 1. Install the CLI 10 | 11 | When working with Google Cloud Platform it is easiest to work with the [gcloud CLI](https://cloud.google.com/sdk/docs/install). 12 | 13 | For example, on MacOS using Homebrew: 14 | 15 | ```sh 16 | brew install --cask google-cloud-sdk 17 | ``` 18 | 19 | Authenticate with the CLI. 20 | 21 | ```sh 22 | gcloud auth login 23 | ``` 24 | 25 | ## 2. Project setup 26 | 27 | Create a project. Accept the auto-generated project ID at the prompt. 28 | 29 | ```sh 30 | gcloud projects create --set-as-default --name="my app" 31 | ``` 32 | 33 | 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. 34 | 35 | ```sh 36 | PROJECT_ID=$(gcloud projects list \ 37 | --format='value(projectId)' \ 38 | --filter='name="my app"') 39 | 40 | PROJECT_NUMBER=$(gcloud projects list \ 41 | --format='value(projectNumber)' \ 42 | --filter='name="my app"') 43 | 44 | echo $PROJECT_ID $PROJECT_NUMBER 45 | ``` 46 | 47 | Find your billing account ID. 48 | 49 | ```sh 50 | gcloud billing accounts list 51 | ``` 52 | 53 | Add your billing account from the prior command to the project. 54 | 55 | ```sh 56 | gcloud billing projects link $PROJECT_ID \ 57 | --billing-account=[billing_account_id] 58 | ``` 59 | 60 | Enable the required APIs. 61 | 62 | ```sh 63 | gcloud services enable run.googleapis.com \ 64 | cloudbuild.googleapis.com 65 | ``` 66 | 67 | Update the service account permissions to have access to Cloud Build. 68 | 69 | ```sh 70 | gcloud projects add-iam-policy-binding $PROJECT_ID \ 71 | --member=serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \ 72 | --role=roles/run.builder 73 | ``` 74 | 75 | ## 3. Hello World 76 | 77 | Start your project with "create-hono" command. Select `nodejs`. 78 | 79 | ```sh 80 | npm create hono@latest my-app 81 | ``` 82 | 83 | Move to `my-app` and install the dependencies. 84 | 85 | ```sh 86 | cd my-app 87 | npm i 88 | ``` 89 | 90 | Update the port in `src/index.ts` to be `8080`. 91 | 92 | 93 | ```ts 94 | import { serve } from '@hono/node-server' 95 | import { Hono } from 'hono' 96 | 97 | const app = new Hono() 98 | 99 | app.get('/', (c) => { 100 | return c.text('Hello Hono!') 101 | }) 102 | 103 | serve({ 104 | fetch: app.fetch, 105 | port: 3000 // [!code --] 106 | port: 8080 // [!code ++] 107 | }, (info) => { 108 | console.log(`Server is running on http://localhost:${info.port}`) 109 | }) 110 | ``` 111 | 112 | Run the development server locally. Then, access http://localhost:8080 in your Web browser. 113 | 114 | ```sh 115 | npm run dev 116 | ``` 117 | 118 | ## 4. Deploy 119 | 120 | Start the deployment and follow the interactive prompts (E.g., select a region). 121 | 122 | ```sh 123 | gcloud run deploy my-app --source . --allow-unauthenticated 124 | ``` 125 | 126 | ## Changing runtimes 127 | 128 | 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. 129 | 130 | For information on containerizing please refer to: 131 | 132 | - [Nodejs](/docs/getting-started/nodejs#building-deployment) 133 | - [Bun](https://bun.sh/guides/ecosystem/docker) 134 | - [Deno](https://docs.deno.com/examples/google_cloud_run_tutorial) 135 | -------------------------------------------------------------------------------- /docs/getting-started/lambda-edge.md: -------------------------------------------------------------------------------- 1 | # Lambda@Edge 2 | 3 | [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. 4 | 5 | Hono supports Lambda@Edge with the Node.js 18+ environment. 6 | 7 | ## 1. Setup 8 | 9 | When creating the application on Lambda@Edge, 10 | [CDK](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-cdk.html) 11 | is useful to setup the functions such as CloudFront, IAM Role, API Gateway, and others. 12 | 13 | Initialize your project with the `cdk` CLI. 14 | 15 | ::: code-group 16 | 17 | ```sh [npm] 18 | mkdir my-app 19 | cd my-app 20 | cdk init app -l typescript 21 | npm i hono 22 | mkdir lambda 23 | ``` 24 | 25 | ```sh [yarn] 26 | mkdir my-app 27 | cd my-app 28 | cdk init app -l typescript 29 | yarn add hono 30 | mkdir lambda 31 | ``` 32 | 33 | ```sh [pnpm] 34 | mkdir my-app 35 | cd my-app 36 | cdk init app -l typescript 37 | pnpm add hono 38 | mkdir lambda 39 | ``` 40 | 41 | ```sh [bun] 42 | mkdir my-app 43 | cd my-app 44 | cdk init app -l typescript 45 | bun add hono 46 | mkdir lambda 47 | ``` 48 | 49 | ::: 50 | 51 | ## 2. Hello World 52 | 53 | Edit `lambda/index_edge.ts`. 54 | 55 | ```ts 56 | import { Hono } from 'hono' 57 | import { handle } from 'hono/lambda-edge' 58 | 59 | const app = new Hono() 60 | 61 | app.get('/', (c) => c.text('Hello Hono on Lambda@Edge!')) 62 | 63 | export const handler = handle(app) 64 | ``` 65 | 66 | ## 3. Deploy 67 | 68 | Edit `bin/my-app.ts`. 69 | 70 | ```ts 71 | #!/usr/bin/env node 72 | import 'source-map-support/register' 73 | import * as cdk from 'aws-cdk-lib' 74 | import { MyAppStack } from '../lib/my-app-stack' 75 | 76 | const app = new cdk.App() 77 | new MyAppStack(app, 'MyAppStack', { 78 | env: { 79 | account: process.env.CDK_DEFAULT_ACCOUNT, 80 | region: 'us-east-1', 81 | }, 82 | }) 83 | ``` 84 | 85 | Edit `lambda/cdk-stack.ts`. 86 | 87 | ```ts 88 | import { Construct } from 'constructs' 89 | import * as cdk from 'aws-cdk-lib' 90 | import * as cloudfront from 'aws-cdk-lib/aws-cloudfront' 91 | import * as origins from 'aws-cdk-lib/aws-cloudfront-origins' 92 | import * as lambda from 'aws-cdk-lib/aws-lambda' 93 | import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' 94 | import * as s3 from 'aws-cdk-lib/aws-s3' 95 | 96 | export class MyAppStack extends cdk.Stack { 97 | public readonly edgeFn: lambda.Function 98 | 99 | constructor(scope: Construct, id: string, props?: cdk.StackProps) { 100 | super(scope, id, props) 101 | const edgeFn = new NodejsFunction(this, 'edgeViewer', { 102 | entry: 'lambda/index_edge.ts', 103 | handler: 'handler', 104 | runtime: lambda.Runtime.NODEJS_20_X, 105 | }) 106 | 107 | // Upload any html 108 | const originBucket = new s3.Bucket(this, 'originBucket') 109 | 110 | new cloudfront.Distribution(this, 'Cdn', { 111 | defaultBehavior: { 112 | origin: new origins.S3Origin(originBucket), 113 | edgeLambdas: [ 114 | { 115 | functionVersion: edgeFn.currentVersion, 116 | eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST, 117 | }, 118 | ], 119 | }, 120 | }) 121 | } 122 | } 123 | ``` 124 | 125 | Finally, run the command to deploy: 126 | 127 | ```sh 128 | cdk deploy 129 | ``` 130 | 131 | ## Callback 132 | 133 | If you want to add Basic Auth and continue with request processing after verification, you can use `c.env.callback()` 134 | 135 | ```ts 136 | import { Hono } from 'hono' 137 | import { basicAuth } from 'hono/basic-auth' 138 | import type { Callback, CloudFrontRequest } from 'hono/lambda-edge' 139 | import { handle } from 'hono/lambda-edge' 140 | 141 | type Bindings = { 142 | callback: Callback 143 | request: CloudFrontRequest 144 | } 145 | 146 | const app = new Hono<{ Bindings: Bindings }>() 147 | 148 | app.get( 149 | '*', 150 | basicAuth({ 151 | username: 'hono', 152 | password: 'acoolproject', 153 | }) 154 | ) 155 | 156 | app.get('/', async (c, next) => { 157 | await next() 158 | c.env.callback(null, c.env.request) 159 | }) 160 | 161 | export const handler = handle(app) 162 | ``` 163 | -------------------------------------------------------------------------------- /docs/getting-started/netlify.md: -------------------------------------------------------------------------------- 1 | # Netlify 2 | 3 | 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. 4 | 5 | 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. 6 | 7 | ## 1. Setup 8 | 9 | A starter for Netlify is available. 10 | Start your project with "create-hono" command. 11 | Select `netlify` template for this example. 12 | 13 | ::: code-group 14 | 15 | ```sh [npm] 16 | npm create hono@latest my-app 17 | ``` 18 | 19 | ```sh [yarn] 20 | yarn create hono my-app 21 | ``` 22 | 23 | ```sh [pnpm] 24 | pnpm create hono my-app 25 | ``` 26 | 27 | ```sh [bun] 28 | bun create hono@latest my-app 29 | ``` 30 | 31 | ```sh [deno] 32 | deno init --npm hono my-app 33 | ``` 34 | 35 | ::: 36 | 37 | Move into `my-app`. 38 | 39 | ## 2. Hello World 40 | 41 | Edit `netlify/edge-functions/index.ts`: 42 | 43 | ```ts 44 | import { Hono } from 'jsr:@hono/hono' 45 | import { handle } from 'jsr:@hono/hono/netlify' 46 | 47 | const app = new Hono() 48 | 49 | app.get('/', (c) => { 50 | return c.text('Hello Hono!') 51 | }) 52 | 53 | export default handle(app) 54 | ``` 55 | 56 | ## 3. Run 57 | 58 | Run the development server with Netlify CLI. Then, access `http://localhost:8888` in your Web browser. 59 | 60 | ```sh 61 | netlify dev 62 | ``` 63 | 64 | ## 4. Deploy 65 | 66 | You can deploy with a `netlify deploy` command. 67 | 68 | ```sh 69 | netlify deploy --prod 70 | ``` 71 | 72 | ## `Context` 73 | 74 | You can access the Netlify's `Context` through `c.env`: 75 | 76 | ```ts 77 | import { Hono } from 'jsr:@hono/hono' 78 | import { handle } from 'jsr:@hono/hono/netlify' 79 | 80 | // Import the type definition 81 | import type { Context } from 'https://edge.netlify.com/' 82 | 83 | export type Env = { 84 | Bindings: { 85 | context: Context 86 | } 87 | } 88 | 89 | const app = new Hono() 90 | 91 | app.get('/country', (c) => 92 | c.json({ 93 | 'You are in': c.env.context.geo.country?.name, 94 | }) 95 | ) 96 | 97 | export default handle(app) 98 | ``` 99 | -------------------------------------------------------------------------------- /docs/getting-started/service-worker.md: -------------------------------------------------------------------------------- 1 | # Service Worker 2 | 3 | [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) is a script that runs in the background of the browser to handle tasks like caching and push notifications. Using a Service Worker adapter, you can run applications made with Hono as [FetchEvent](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent) handler within the browser. 4 | 5 | This page shows an example of creating a project using [Vite](https://vitejs.dev/). 6 | 7 | ## 1. Setup 8 | 9 | First, create and move to your project directory: 10 | 11 | ```sh 12 | mkdir my-app 13 | cd my-app 14 | ``` 15 | 16 | Create the necessary files for the project. Make a `package.json` file with the following: 17 | 18 | ```json 19 | { 20 | "name": "my-app", 21 | "private": true, 22 | "scripts": { 23 | "dev": "vite dev" 24 | }, 25 | "type": "module" 26 | } 27 | ``` 28 | 29 | Similarly, create a `tsconfig.json` file with the following: 30 | 31 | ```json 32 | { 33 | "compilerOptions": { 34 | "target": "ES2020", 35 | "module": "ESNext", 36 | "lib": ["ES2020", "DOM", "WebWorker"], 37 | "moduleResolution": "bundler" 38 | }, 39 | "include": ["./"], 40 | "exclude": ["node_modules"] 41 | } 42 | ``` 43 | 44 | Next, install the necessary modules. 45 | 46 | ::: code-group 47 | 48 | ```sh [npm] 49 | npm i hono 50 | npm i -D vite 51 | ``` 52 | 53 | ```sh [yarn] 54 | yarn add hono 55 | yarn add -D vite 56 | ``` 57 | 58 | ```sh [pnpm] 59 | pnpm add hono 60 | pnpm add -D vite 61 | ``` 62 | 63 | ```sh [bun] 64 | bun add hono 65 | bun add -D vite 66 | ``` 67 | 68 | ::: 69 | 70 | ## 2. Hello World 71 | 72 | Edit `index.html`: 73 | 74 | ```html 75 | 76 | 77 | 78 | Hello World by Service Worker 79 | 80 | 81 | 82 | ``` 83 | 84 | `main.ts` is a script to register the Service Worker: 85 | 86 | ```ts 87 | function register() { 88 | navigator.serviceWorker 89 | .register('/sw.ts', { scope: '/sw', type: 'module' }) 90 | .then( 91 | function (_registration) { 92 | console.log('Register Service Worker: Success') 93 | }, 94 | function (_error) { 95 | console.log('Register Service Worker: Error') 96 | } 97 | ) 98 | } 99 | function start() { 100 | navigator.serviceWorker 101 | .getRegistrations() 102 | .then(function (registrations) { 103 | for (const registration of registrations) { 104 | console.log('Unregister Service Worker') 105 | registration.unregister() 106 | } 107 | register() 108 | }) 109 | } 110 | start() 111 | ``` 112 | 113 | 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`. 114 | 115 | ```ts 116 | // To support types 117 | // https://github.com/microsoft/TypeScript/issues/14877 118 | declare const self: ServiceWorkerGlobalScope 119 | 120 | import { Hono } from 'hono' 121 | import { handle } from 'hono/service-worker' 122 | 123 | const app = new Hono().basePath('/sw') 124 | app.get('/', (c) => c.text('Hello World')) 125 | 126 | self.addEventListener('fetch', handle(app)) 127 | ``` 128 | 129 | ## 3. Run 130 | 131 | Start the development server. 132 | 133 | ::: code-group 134 | 135 | ```sh [npm] 136 | npm run dev 137 | ``` 138 | 139 | ```sh [yarn] 140 | yarn dev 141 | ``` 142 | 143 | ```sh [pnpm] 144 | pnpm run dev 145 | ``` 146 | 147 | ```sh [bun] 148 | bun run dev 149 | ``` 150 | 151 | ::: 152 | 153 | 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. 154 | -------------------------------------------------------------------------------- /docs/getting-started/supabase-functions.md: -------------------------------------------------------------------------------- 1 | # Supabase Edge Functions 2 | 3 | [Supabase](https://supabase.com/) is an open-source alternative to Firebase, offering a suite of tools similar to Firebase's capabilities, including database, authentication, storage, and now, serverless functions. 4 | 5 | Supabase Edge Functions are server-side TypeScript functions that are distributed globally, running closer to your users for improved performance. These functions are developed using [Deno](https://deno.com/), which brings several benefits, including improved security and a modern JavaScript/TypeScript runtime. 6 | 7 | Here's how you can get started with Supabase Edge Functions: 8 | 9 | ## 1. Setup 10 | 11 | ### Prerequisites 12 | 13 | Before you begin, make sure you have the Supabase CLI installed. If you haven't installed it yet, follow the instructions in the [official documentation](https://supabase.com/docs/guides/cli/getting-started). 14 | 15 | ### Creating a New Project 16 | 17 | 1. Open your terminal or command prompt. 18 | 19 | 2. Create a new Supabase project in a directory on your local machine by running: 20 | 21 | ```bash 22 | supabase init 23 | 24 | ``` 25 | 26 | This command initializes a new Supabase project in the current directory. 27 | 28 | ### Adding an Edge Function 29 | 30 | 3. Inside your Supabase project, create a new Edge Function named `hello-world`: 31 | 32 | ```bash 33 | supabase functions new hello-world 34 | 35 | ``` 36 | 37 | This command creates a new Edge Function with the specified name in your project. 38 | 39 | ## 2. Hello World 40 | 41 | Edit the `hello-world` function by modifying the file `supabase/functions/hello-world/index.ts`: 42 | 43 | ```ts 44 | import { Hono } from 'jsr:@hono/hono' 45 | 46 | // change this to your function name 47 | const functionName = 'hello-world' 48 | const app = new Hono().basePath(`/${functionName}`) 49 | 50 | app.get('/hello', (c) => c.text('Hello from hono-server!')) 51 | 52 | Deno.serve(app.fetch) 53 | ``` 54 | 55 | ## 3. Run 56 | 57 | To run the function locally, use the following command: 58 | 59 | 1. Use the following command to serve the function: 60 | 61 | ```bash 62 | supabase start # start the supabase stack 63 | supabase functions serve --no-verify-jwt # start the Functions watcher 64 | ``` 65 | 66 | The `--no-verify-jwt` flag allows you to bypass JWT verification during local development. 67 | 68 | 2. Make a GET request using cURL or Postman to `http://127.0.0.1:54321/functions/v1/hello-world/hello`: 69 | 70 | ```bash 71 | curl --location 'http://127.0.0.1:54321/functions/v1/hello-world/hello' 72 | ``` 73 | 74 | This request should return the text "Hello from hono-server!". 75 | 76 | ## 4. Deploy 77 | 78 | You can deploy all of your Edge Functions in Supabase with a single command: 79 | 80 | ```bash 81 | supabase functions deploy 82 | ``` 83 | 84 | Alternatively, you can deploy individual Edge Functions by specifying the name of the function in the deploy command: 85 | 86 | ```bash 87 | supabase functions deploy hello-world 88 | 89 | ``` 90 | 91 | For more deployment methods, visit the Supabase documentation on [Deploying to Production](https://supabase.com/docs/guides/functions/deploy). 92 | -------------------------------------------------------------------------------- /docs/getting-started/vercel.md: -------------------------------------------------------------------------------- 1 | # Vercel 2 | 3 | Vercel is the platform for frontend developers, providing the speed and reliability innovators need to create at the moment of inspiration. This section introduces Next.js running on Vercel. 4 | 5 | Next.js is a flexible React framework that gives you building blocks to create fast web applications. 6 | 7 | In Next.js, [Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions) allows you to create dynamic APIs on Edge Runtime such as Vercel. 8 | With Hono, you can write APIs with the same syntax as other runtimes and use many middleware. 9 | 10 | ## 1. Setup 11 | 12 | A starter for Next.js is available. 13 | Start your project with "create-hono" command. 14 | Select `nextjs` template for this example. 15 | 16 | ::: code-group 17 | 18 | ```sh [npm] 19 | npm create hono@latest my-app 20 | ``` 21 | 22 | ```sh [yarn] 23 | yarn create hono my-app 24 | ``` 25 | 26 | ```sh [pnpm] 27 | pnpm create hono my-app 28 | ``` 29 | 30 | ```sh [bun] 31 | bun create hono@latest my-app 32 | ``` 33 | 34 | ```sh [deno] 35 | deno init --npm hono my-app 36 | ``` 37 | 38 | ::: 39 | 40 | Move into `my-app` and install the dependencies. 41 | 42 | ::: code-group 43 | 44 | ```sh [npm] 45 | cd my-app 46 | npm i 47 | ``` 48 | 49 | ```sh [yarn] 50 | cd my-app 51 | yarn 52 | ``` 53 | 54 | ```sh [pnpm] 55 | cd my-app 56 | pnpm i 57 | ``` 58 | 59 | ```sh [bun] 60 | cd my-app 61 | bun i 62 | ``` 63 | 64 | ::: 65 | 66 | ## 2. Hello World 67 | 68 | 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. 69 | 70 | ```ts 71 | import { Hono } from 'hono' 72 | import { handle } from 'hono/vercel' 73 | 74 | export const runtime = 'edge' 75 | 76 | const app = new Hono().basePath('/api') 77 | 78 | app.get('/hello', (c) => { 79 | return c.json({ 80 | message: 'Hello Next.js!', 81 | }) 82 | }) 83 | 84 | export const GET = handle(app) 85 | export const POST = handle(app) 86 | ``` 87 | 88 | If you use the Pages Router, Edit `pages/api/[[...route]].ts`. 89 | 90 | ```ts 91 | import { Hono } from 'hono' 92 | import { handle } from 'hono/vercel' 93 | import type { PageConfig } from 'next' 94 | 95 | export const config: PageConfig = { 96 | runtime: 'edge', 97 | } 98 | 99 | const app = new Hono().basePath('/api') 100 | 101 | app.get('/hello', (c) => { 102 | return c.json({ 103 | message: 'Hello Next.js!', 104 | }) 105 | }) 106 | 107 | export default handle(app) 108 | ``` 109 | 110 | ## 3. Run 111 | 112 | Run the development server locally. Then, access `http://localhost:3000` in your Web browser. 113 | 114 | ::: code-group 115 | 116 | ```sh [npm] 117 | npm run dev 118 | ``` 119 | 120 | ```sh [yarn] 121 | yarn dev 122 | ``` 123 | 124 | ```sh [pnpm] 125 | pnpm dev 126 | ``` 127 | 128 | ```sh [bun] 129 | bun run dev 130 | ``` 131 | 132 | ::: 133 | 134 | Now, `/api/hello` just returns JSON, but if you build React UIs, you can create a full-stack application with Hono. 135 | 136 | ## 4. Deploy 137 | 138 | If you have a Vercel account, you can deploy by linking the Git repository. 139 | 140 | ## Node.js 141 | 142 | You can also run Hono on Next.js running on the Node.js runtime. 143 | 144 | ### App Router 145 | 146 | For the App Router, you can simply set the runtime to `nodejs` in your route handler: 147 | 148 | ```ts 149 | import { Hono } from 'hono' 150 | import { handle } from 'hono/vercel' 151 | 152 | export const runtime = 'nodejs' 153 | 154 | const app = new Hono().basePath('/api') 155 | 156 | app.get('/hello', (c) => { 157 | return c.json({ 158 | message: 'Hello from Hono!', 159 | }) 160 | }) 161 | 162 | export const GET = handle(app) 163 | export const POST = handle(app) 164 | ``` 165 | 166 | ### Pages Router 167 | 168 | For the Pages Router, you'll need to install the Node.js adapter first: 169 | 170 | ::: code-group 171 | 172 | ```sh [npm] 173 | npm i @hono/node-server 174 | ``` 175 | 176 | ```sh [yarn] 177 | yarn add @hono/node-server 178 | ``` 179 | 180 | ```sh [pnpm] 181 | pnpm add @hono/node-server 182 | ``` 183 | 184 | ```sh [bun] 185 | bun add @hono/node-server 186 | ``` 187 | 188 | ::: 189 | 190 | Then, you can utilize the `handle` function imported from `@hono/node-server/vercel`: 191 | 192 | ```ts 193 | import { Hono } from 'hono' 194 | import { handle } from '@hono/node-server/vercel' 195 | import type { PageConfig } from 'next' 196 | 197 | export const config: PageConfig = { 198 | api: { 199 | bodyParser: false, 200 | }, 201 | } 202 | 203 | const app = new Hono().basePath('/api') 204 | 205 | app.get('/hello', (c) => { 206 | return c.json({ 207 | message: 'Hello from Hono!', 208 | }) 209 | }) 210 | 211 | export default handle(app) 212 | ``` 213 | 214 | 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: 215 | 216 | ```text 217 | NODEJS_HELPERS=0 218 | ``` 219 | -------------------------------------------------------------------------------- /docs/guides/best-practices.md: -------------------------------------------------------------------------------- 1 | # Best Practices 2 | 3 | Hono is very flexible. You can write your app as you like. 4 | However, there are best practices that are better to follow. 5 | 6 | ## Don't make "Controllers" when possible 7 | 8 | When possible, you should not create "Ruby on Rails-like Controllers". 9 | 10 | ```ts 11 | // 🙁 12 | // A RoR-like Controller 13 | const booksList = (c: Context) => { 14 | return c.json('list books') 15 | } 16 | 17 | app.get('/books', booksList) 18 | ``` 19 | 20 | The issue is related to types. For example, the path parameter cannot be inferred in the Controller without writing complex generics. 21 | 22 | ```ts 23 | // 🙁 24 | // A RoR-like Controller 25 | const bookPermalink = (c: Context) => { 26 | const id = c.req.param('id') // Can't infer the path param 27 | return c.json(`get ${id}`) 28 | } 29 | ``` 30 | 31 | Therefore, you don't need to create RoR-like controllers and should write handlers directly after path definitions. 32 | 33 | ```ts 34 | // 😃 35 | app.get('/books/:id', (c) => { 36 | const id = c.req.param('id') // Can infer the path param 37 | return c.json(`get ${id}`) 38 | }) 39 | ``` 40 | 41 | ## `factory.createHandlers()` in `hono/factory` 42 | 43 | If you still want to create a RoR-like Controller, use `factory.createHandlers()` in [`hono/factory`](/docs/helpers/factory). If you use this, type inference will work correctly. 44 | 45 | ```ts 46 | import { createFactory } from 'hono/factory' 47 | import { logger } from 'hono/logger' 48 | 49 | // ... 50 | 51 | // 😃 52 | const factory = createFactory() 53 | 54 | const middleware = factory.createMiddleware(async (c, next) => { 55 | c.set('foo', 'bar') 56 | await next() 57 | }) 58 | 59 | const handlers = factory.createHandlers(logger(), middleware, (c) => { 60 | return c.json(c.var.foo) 61 | }) 62 | 63 | app.get('/api', ...handlers) 64 | ``` 65 | 66 | ## Building a larger application 67 | 68 | Use `app.route()` to build a larger application without creating "Ruby on Rails-like Controllers". 69 | 70 | If your application has `/authors` and `/books` endpoints and you wish to separate files from `index.ts`, create `authors.ts` and `books.ts`. 71 | 72 | ```ts 73 | // authors.ts 74 | import { Hono } from 'hono' 75 | 76 | const app = new Hono() 77 | 78 | app.get('/', (c) => c.json('list authors')) 79 | app.post('/', (c) => c.json('create an author', 201)) 80 | app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`)) 81 | 82 | export default app 83 | ``` 84 | 85 | ```ts 86 | // books.ts 87 | import { Hono } from 'hono' 88 | 89 | const app = new Hono() 90 | 91 | app.get('/', (c) => c.json('list books')) 92 | app.post('/', (c) => c.json('create a book', 201)) 93 | app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`)) 94 | 95 | export default app 96 | ``` 97 | 98 | Then, import them and mount on the paths `/authors` and `/books` with `app.route()`. 99 | 100 | ```ts 101 | // index.ts 102 | import { Hono } from 'hono' 103 | import authors from './authors' 104 | import books from './books' 105 | 106 | const app = new Hono() 107 | 108 | // 😃 109 | app.route('/authors', authors) 110 | app.route('/books', books) 111 | 112 | export default app 113 | ``` 114 | 115 | ### If you want to use RPC features 116 | 117 | The code above works well for normal use cases. 118 | However, if you want to use the `RPC` feature, you can get the correct type by chaining as follows. 119 | 120 | ```ts 121 | // authors.ts 122 | import { Hono } from 'hono' 123 | 124 | const app = new Hono() 125 | .get('/', (c) => c.json('list authors')) 126 | .post('/', (c) => c.json('create an author', 201)) 127 | .get('/:id', (c) => c.json(`get ${c.req.param('id')}`)) 128 | 129 | export default app 130 | ``` 131 | 132 | If you pass the type of the `app` to `hc`, it will get the correct type. 133 | 134 | ```ts 135 | import app from './authors' 136 | import { hc } from 'hono/client' 137 | 138 | // 😃 139 | const client = hc('http://localhost') // Typed correctly 140 | ``` 141 | 142 | For more detailed information, please see [the RPC page](/docs/guides/rpc#using-rpc-with-larger-applications). 143 | -------------------------------------------------------------------------------- /docs/guides/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | See the [Examples section](/examples/). 4 | -------------------------------------------------------------------------------- /docs/guides/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | This guide is a collection of frequently asked questions (FAQ) about Hono and how to resolve them. 4 | 5 | ## Is there an official Renovate config for Hono? 6 | 7 | The Hono teams does not currently maintain [Renovate](https://github.com/renovatebot/renovate) Configuration. 8 | Therefore, please use third-party renovate-config as follows. 9 | 10 | In your `renovate.json` : 11 | 12 | ```json 13 | // renovate.json 14 | { 15 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 16 | "extends": [ 17 | "github>shinGangan/renovate-config-hono" // [!code ++] 18 | ] 19 | } 20 | ``` 21 | 22 | see [renovate-config-hono](https://github.com/shinGangan/renovate-config-hono) repository for more details. 23 | -------------------------------------------------------------------------------- /docs/guides/helpers.md: -------------------------------------------------------------------------------- 1 | # Helpers 2 | 3 | Helpers are available to assist in developing your application. Unlike middleware, they don't act as handlers, but rather provide useful functions. 4 | 5 | For instance, here's how to use the [Cookie helper](/docs/helpers/cookie): 6 | 7 | ```ts 8 | import { getCookie, setCookie } from 'hono/cookie' 9 | 10 | const app = new Hono() 11 | 12 | app.get('/cookie', (c) => { 13 | const yummyCookie = getCookie(c, 'yummy_cookie') 14 | // ... 15 | setCookie(c, 'delicious_cookie', 'macha') 16 | // 17 | }) 18 | ``` 19 | 20 | ## Available Helpers 21 | 22 | - [Accepts](/docs/helpers/accepts) 23 | - [Adapter](/docs/helpers/adapter) 24 | - [Cookie](/docs/helpers/cookie) 25 | - [css](/docs/helpers/css) 26 | - [Dev](/docs/helpers/dev) 27 | - [Factory](/docs/helpers/factory) 28 | - [html](/docs/helpers/html) 29 | - [JWT](/docs/helpers/jwt) 30 | - [SSG](/docs/helpers/ssg) 31 | - [Streaming](/docs/helpers/streaming) 32 | - [Testing](/docs/helpers/testing) 33 | - [WebSocket](/docs/helpers/websocket) 34 | -------------------------------------------------------------------------------- /docs/guides/others.md: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | 3 | ## Contributing 4 | 5 | Contributions Welcome! You can contribute in the following ways. 6 | 7 | - Create an Issue - Propose a new feature. Report a bug. 8 | - Pull Request - Fix a bug and typo. Refactor the code. 9 | - Create third-party middleware - Instruct below. 10 | - Share - Share your thoughts on the Blog, X(Twitter), and others. 11 | - Make your application - Please try to use Hono. 12 | 13 | For more details, see [Contribution Guide](https://github.com/honojs/hono/blob/main/docs/CONTRIBUTING.md). 14 | 15 | ## Sponsoring 16 | 17 | You can sponsor Hono authors via the GitHub sponsor program. 18 | 19 | - [Sponsor @yusukebe on GitHub Sponsors](https://github.com/sponsors/yusukebe) 20 | - [Sponsor @usualoma on GitHub Sponsors](https://github.com/sponsors/usualoma) 21 | 22 | ## Other Resources 23 | 24 | - GitHub repository: https://github.com/honojs 25 | - npm registry: https://www.npmjs.com/package/hono 26 | - JSR: https://jsr.io/@hono/hono 27 | -------------------------------------------------------------------------------- /docs/guides/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | [Vitest]: https://vitest.dev/ 4 | 5 | Testing is important. 6 | In actuality, it is easy to test Hono's applications. 7 | The way to create a test environment differs from each runtime, but the basic steps are the same. 8 | In this section, let's test with Cloudflare Workers and [Vitest]. 9 | 10 | ::: tip 11 | Cloudflare recommends using [Vitest] with [@cloudflare/vitest-pool-workers](https://www.npmjs.com/package/@cloudflare/vitest-pool-workers). For more details, please refer to [Vitest integration](https://developers.cloudflare.com/workers/testing/vitest-integration/) in the Cloudflare Workers docs. 12 | ::: 13 | 14 | ## Request and Response 15 | 16 | All you do is create a Request and pass it to the Hono application to validate the Response. 17 | And, you can use `app.request` the useful method. 18 | 19 | ::: tip 20 | For a typed test client see the [testing helper](/docs/helpers/testing). 21 | ::: 22 | 23 | For example, consider an application that provides the following REST API. 24 | 25 | ```ts 26 | app.get('/posts', (c) => { 27 | return c.text('Many posts') 28 | }) 29 | 30 | app.post('/posts', (c) => { 31 | return c.json( 32 | { 33 | message: 'Created', 34 | }, 35 | 201, 36 | { 37 | 'X-Custom': 'Thank you', 38 | } 39 | ) 40 | }) 41 | ``` 42 | 43 | Make a request to `GET /posts` and test the response. 44 | 45 | ```ts 46 | describe('Example', () => { 47 | test('GET /posts', async () => { 48 | const res = await app.request('/posts') 49 | expect(res.status).toBe(200) 50 | expect(await res.text()).toBe('Many posts') 51 | }) 52 | }) 53 | ``` 54 | 55 | To make a request to `POST /posts`, do the following. 56 | 57 | ```ts 58 | test('POST /posts', async () => { 59 | const res = await app.request('/posts', { 60 | method: 'POST', 61 | }) 62 | expect(res.status).toBe(201) 63 | expect(res.headers.get('X-Custom')).toBe('Thank you') 64 | expect(await res.json()).toEqual({ 65 | message: 'Created', 66 | }) 67 | }) 68 | ``` 69 | 70 | To make a request to `POST /posts` with `JSON` data, do the following. 71 | 72 | ```ts 73 | test('POST /posts', async () => { 74 | const res = await app.request('/posts', { 75 | method: 'POST', 76 | body: JSON.stringify({ message: 'hello hono' }), 77 | headers: new Headers({ 'Content-Type': 'application/json' }), 78 | }) 79 | expect(res.status).toBe(201) 80 | expect(res.headers.get('X-Custom')).toBe('Thank you') 81 | expect(await res.json()).toEqual({ 82 | message: 'Created', 83 | }) 84 | }) 85 | ``` 86 | 87 | To make a request to `POST /posts` with `multipart/form-data` data, do the following. 88 | 89 | ```ts 90 | test('POST /posts', async () => { 91 | const formData = new FormData() 92 | formData.append('message', 'hello') 93 | const res = await app.request('/posts', { 94 | method: 'POST', 95 | body: formData, 96 | }) 97 | expect(res.status).toBe(201) 98 | expect(res.headers.get('X-Custom')).toBe('Thank you') 99 | expect(await res.json()).toEqual({ 100 | message: 'Created', 101 | }) 102 | }) 103 | ``` 104 | 105 | You can also pass an instance of the Request class. 106 | 107 | ```ts 108 | test('POST /posts', async () => { 109 | const req = new Request('http://localhost/posts', { 110 | method: 'POST', 111 | }) 112 | const res = await app.request(req) 113 | expect(res.status).toBe(201) 114 | expect(res.headers.get('X-Custom')).toBe('Thank you') 115 | expect(await res.json()).toEqual({ 116 | message: 'Created', 117 | }) 118 | }) 119 | ``` 120 | 121 | In this way, you can test it as like an End-to-End. 122 | 123 | ## Env 124 | 125 | To set `c.env` for testing, you can pass it as the 3rd parameter to `app.request`. This is useful for mocking values like [Cloudflare Workers Bindings](https://hono.dev/getting-started/cloudflare-workers#bindings): 126 | 127 | ```ts 128 | const MOCK_ENV = { 129 | API_HOST: 'example.com', 130 | DB: { 131 | prepare: () => { 132 | /* mocked D1 */ 133 | }, 134 | }, 135 | } 136 | 137 | test('GET /posts', async () => { 138 | const res = await app.request('/posts', {}, MOCK_ENV) 139 | }) 140 | ``` 141 | -------------------------------------------------------------------------------- /docs/helpers/accepts.md: -------------------------------------------------------------------------------- 1 | # Accepts Helper 2 | 3 | Accepts Helper helps to handle Accept headers in the Requests. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { accepts } from 'hono/accepts' 10 | ``` 11 | 12 | ## `accepts()` 13 | 14 | The `accepts()` function looks at the Accept header, such as Accept-Encoding and Accept-Language, and returns the proper value. 15 | 16 | ```ts 17 | import { accepts } from 'hono/accepts' 18 | 19 | app.get('/', (c) => { 20 | const accept = accepts(c, { 21 | header: 'Accept-Language', 22 | supports: ['en', 'ja', 'zh'], 23 | default: 'en', 24 | }) 25 | return c.json({ lang: accept }) 26 | }) 27 | ``` 28 | 29 | ### `AcceptHeader` type 30 | 31 | The definition of the `AcceptHeader` type is as follows. 32 | 33 | ```ts 34 | export type AcceptHeader = 35 | | 'Accept' 36 | | 'Accept-Charset' 37 | | 'Accept-Encoding' 38 | | 'Accept-Language' 39 | | 'Accept-Patch' 40 | | 'Accept-Post' 41 | | 'Accept-Ranges' 42 | ``` 43 | 44 | ## Options 45 | 46 | ### header: `AcceptHeader` 47 | 48 | The target accept header. 49 | 50 | ### supports: `string[]` 51 | 52 | The header values which your application supports. 53 | 54 | ### default: `string` 55 | 56 | The default values. 57 | 58 | ### match: `(accepts: Accept[], config: acceptsConfig) => string` 59 | 60 | The custom match function. 61 | -------------------------------------------------------------------------------- /docs/helpers/adapter.md: -------------------------------------------------------------------------------- 1 | # Adapter Helper 2 | 3 | The Adapter Helper provides a seamless way to interact with various platforms through a unified interface. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { env, getRuntimeKey } from 'hono/adapter' 10 | ``` 11 | 12 | ## `env()` 13 | 14 | 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. 15 | 16 | ```ts 17 | import { env } from 'hono/adapter' 18 | 19 | app.get('/env', (c) => { 20 | // NAME is process.env.NAME on Node.js or Bun 21 | // NAME is the value written in `wrangler.toml` on Cloudflare 22 | const { NAME } = env<{ NAME: string }>(c) 23 | return c.text(NAME) 24 | }) 25 | ``` 26 | 27 | Supported Runtimes, Serverless Platforms and Cloud Services: 28 | 29 | - Cloudflare Workers 30 | - `wrangler.toml` 31 | - `wrangler.jsonc` 32 | - Deno 33 | - [`Deno.env`](https://docs.deno.com/runtime/manual/basics/env_variables) 34 | - `.env` file 35 | - Bun 36 | - [`Bun.env`](https://bun.sh/guides/runtime/set-env) 37 | - `process.env` 38 | - Node.js 39 | - `process.env` 40 | - Vercel 41 | - [Environment Variables on Vercel](https://vercel.com/docs/projects/environment-variables) 42 | - AWS Lambda 43 | - [Environment Variables on AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/samples-blank.html#samples-blank-architecture) 44 | - Lambda@Edge\ 45 | 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. 46 | - Fastly Compute\ 47 | On Fastly Compute, you can use the ConfigStore to manage user-defined data. 48 | - Netlify\ 49 | On Netlify, you can use the [Netlify Contexts](https://docs.netlify.com/site-deploys/overview/#deploy-contexts) to manage user-defined data. 50 | 51 | ### Specify the runtime 52 | 53 | You can specify the runtime to get environment variables by passing the runtime key as the second argument. 54 | 55 | ```ts 56 | app.get('/env', (c) => { 57 | const { NAME } = env<{ NAME: string }>(c, 'workerd') 58 | return c.text(NAME) 59 | }) 60 | ``` 61 | 62 | ## `getRuntimeKey()` 63 | 64 | The `getRuntimeKey()` function returns the identifier of the current runtime. 65 | 66 | ```ts 67 | app.get('/', (c) => { 68 | if (getRuntimeKey() === 'workerd') { 69 | return c.text('You are on Cloudflare') 70 | } else if (getRuntimeKey() === 'bun') { 71 | return c.text('You are on Bun') 72 | } 73 | ... 74 | }) 75 | ``` 76 | 77 | ### Available Runtimes Keys 78 | 79 | 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/): 80 | 81 | - `workerd` - Cloudflare Workers 82 | - `deno` 83 | - `bun` 84 | - `node` 85 | - `edge-light` - Vercel Edge Functions 86 | - `fastly` - Fastly Compute 87 | - `other` - Other unknown runtimes keys 88 | -------------------------------------------------------------------------------- /docs/helpers/conninfo.md: -------------------------------------------------------------------------------- 1 | # ConnInfo Helper 2 | 3 | The ConnInfo Helper helps you to get the connection information. For example, you can get the client's remote address easily. 4 | 5 | ## Import 6 | 7 | ::: code-group 8 | 9 | ```ts [Cloudflare Workers] 10 | import { Hono } from 'hono' 11 | import { getConnInfo } from 'hono/cloudflare-workers' 12 | ``` 13 | 14 | ```ts [Deno] 15 | import { Hono } from 'hono' 16 | import { getConnInfo } from 'hono/deno' 17 | ``` 18 | 19 | ```ts [Bun] 20 | import { Hono } from 'hono' 21 | import { getConnInfo } from 'hono/bun' 22 | ``` 23 | 24 | ```ts [Vercel] 25 | import { Hono } from 'hono' 26 | import { getConnInfo } from 'hono/vercel' 27 | ``` 28 | 29 | ```ts [Lambda@Edge] 30 | import { Hono } from 'hono' 31 | import { getConnInfo } from 'hono/lambda-edge' 32 | ``` 33 | 34 | ```ts [Node.js] 35 | import { Hono } from 'hono' 36 | import { getConnInfo } from '@hono/node-server/conninfo' 37 | ``` 38 | 39 | ::: 40 | 41 | ## Usage 42 | 43 | ```ts 44 | const app = new Hono() 45 | 46 | app.get('/', (c) => { 47 | const info = getConnInfo(c) // info is `ConnInfo` 48 | return c.text(`Your remote address is ${info.remote.address}`) 49 | }) 50 | ``` 51 | 52 | ## Type Definitions 53 | 54 | The type definitions of the values that you can get from `getConnInfo()` are the following: 55 | 56 | ```ts 57 | type AddressType = 'IPv6' | 'IPv4' | undefined 58 | 59 | type NetAddrInfo = { 60 | /** 61 | * Transport protocol type 62 | */ 63 | transport?: 'tcp' | 'udp' 64 | /** 65 | * Transport port number 66 | */ 67 | port?: number 68 | 69 | address?: string 70 | addressType?: AddressType 71 | } & ( 72 | | { 73 | /** 74 | * Host name such as IP Addr 75 | */ 76 | address: string 77 | 78 | /** 79 | * Host name type 80 | */ 81 | addressType: AddressType 82 | } 83 | | {} 84 | ) 85 | 86 | /** 87 | * HTTP Connection information 88 | */ 89 | interface ConnInfo { 90 | /** 91 | * Remote information 92 | */ 93 | remote: NetAddrInfo 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /docs/helpers/cookie.md: -------------------------------------------------------------------------------- 1 | # Cookie Helper 2 | 3 | The Cookie Helper provides an easy interface to manage cookies, enabling developers to set, parse, and delete cookies seamlessly. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { 10 | getCookie, 11 | getSignedCookie, 12 | setCookie, 13 | setSignedCookie, 14 | deleteCookie, 15 | } from 'hono/cookie' 16 | ``` 17 | 18 | ## Usage 19 | 20 | ### Regular cookies 21 | 22 | ```ts 23 | app.get('/cookie', (c) => { 24 | setCookie(c, 'cookie_name', 'cookie_value') 25 | const yummyCookie = getCookie(c, 'cookie_name') 26 | deleteCookie(c, 'cookie_name') 27 | const allCookies = getCookie(c) 28 | // ... 29 | }) 30 | ``` 31 | 32 | ### Signed cookies 33 | 34 | **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. 35 | 36 | ```ts 37 | app.get('/signed-cookie', (c) => { 38 | const secret = 'secret' // make sure it's a large enough string to be secure 39 | 40 | await setSignedCookie(c, 'cookie_name0', 'cookie_value', secret) 41 | const fortuneCookie = await getSignedCookie( 42 | c, 43 | secret, 44 | 'cookie_name0' 45 | ) 46 | deleteCookie(c, 'cookie_name0') 47 | // `getSignedCookie` will return `false` for a specified cookie if the signature was tampered with or is invalid 48 | const allSignedCookies = await getSignedCookie(c, secret) 49 | // ... 50 | }) 51 | ``` 52 | 53 | ## Options 54 | 55 | ### `setCookie` & `setSignedCookie` 56 | 57 | - domain: `string` 58 | - expires: `Date` 59 | - httpOnly: `boolean` 60 | - maxAge: `number` 61 | - path: `string` 62 | - secure: `boolean` 63 | - sameSite: `'Strict'` | `'Lax'` | `'None'` 64 | - priority: `'Low' | 'Medium' | 'High'` 65 | - prefix: `secure` | `'host'` 66 | - partitioned: `boolean` 67 | 68 | Example: 69 | 70 | ```ts 71 | // Regular cookies 72 | setCookie(c, 'great_cookie', 'banana', { 73 | path: '/', 74 | secure: true, 75 | domain: 'example.com', 76 | httpOnly: true, 77 | maxAge: 1000, 78 | expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)), 79 | sameSite: 'Strict', 80 | }) 81 | 82 | // Signed cookies 83 | await setSignedCookie( 84 | c, 85 | 'fortune_cookie', 86 | 'lots-of-money', 87 | 'secret ingredient', 88 | { 89 | path: '/', 90 | secure: true, 91 | domain: 'example.com', 92 | httpOnly: true, 93 | maxAge: 1000, 94 | expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)), 95 | sameSite: 'Strict', 96 | } 97 | ) 98 | ``` 99 | 100 | ### `deleteCookie` 101 | 102 | - path: `string` 103 | - secure: `boolean` 104 | - domain: `string` 105 | 106 | Example: 107 | 108 | ```ts 109 | deleteCookie(c, 'banana', { 110 | path: '/', 111 | secure: true, 112 | domain: 'example.com', 113 | }) 114 | ``` 115 | 116 | `deleteCookie` returns the deleted value: 117 | 118 | ```ts 119 | const deletedCookie = deleteCookie(c, 'delicious_cookie') 120 | ``` 121 | 122 | ## `__Secure-` and `__Host-` prefix 123 | 124 | The Cookie helper supports `__Secure-` and `__Host-` prefix for cookies names. 125 | 126 | If you want to verify if the cookie name has a prefix, specify the prefix option. 127 | 128 | ```ts 129 | const securePrefixCookie = getCookie(c, 'yummy_cookie', 'secure') 130 | const hostPrefixCookie = getCookie(c, 'yummy_cookie', 'host') 131 | 132 | const securePrefixSignedCookie = await getSignedCookie( 133 | c, 134 | secret, 135 | 'fortune_cookie', 136 | 'secure' 137 | ) 138 | const hostPrefixSignedCookie = await getSignedCookie( 139 | c, 140 | secret, 141 | 'fortune_cookie', 142 | 'host' 143 | ) 144 | ``` 145 | 146 | Also, if you wish to specify a prefix when setting the cookie, specify a value for the prefix option. 147 | 148 | ```ts 149 | setCookie(c, 'delicious_cookie', 'macha', { 150 | prefix: 'secure', // or `host` 151 | }) 152 | 153 | await setSignedCookie( 154 | c, 155 | 'delicious_cookie', 156 | 'macha', 157 | 'secret choco chips', 158 | { 159 | prefix: 'secure', // or `host` 160 | } 161 | ) 162 | ``` 163 | 164 | ## Following the best practices 165 | 166 | A New Cookie RFC (a.k.a cookie-bis) and CHIPS include some best practices for Cookie settings that developers should follow. 167 | 168 | - [RFC6265bis-13](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-13) 169 | - `Max-Age`/`Expires` limitation 170 | - `__Host-`/`__Secure-` prefix limitation 171 | - [CHIPS-01](https://www.ietf.org/archive/id/draft-cutler-httpbis-partitioned-cookies-01.html) 172 | - `Partitioned` limitation 173 | 174 | Hono is following the best practices. 175 | The cookie helper will throw an `Error` when parsing cookies under the following conditions: 176 | 177 | - The cookie name starts with `__Secure-`, but `secure` option is not set. 178 | - The cookie name starts with `__Host-`, but `secure` option is not set. 179 | - The cookie name starts with `__Host-`, but `path` is not `/`. 180 | - The cookie name starts with `__Host-`, but `domain` is set. 181 | - The `maxAge` option value is greater than 400 days. 182 | - The `expires` option value is 400 days later than the current time. 183 | -------------------------------------------------------------------------------- /docs/helpers/dev.md: -------------------------------------------------------------------------------- 1 | # Dev Helper 2 | 3 | Dev Helper provides useful methods you can use in development. 4 | 5 | ```ts 6 | import { Hono } from 'hono' 7 | import { getRouterName, showRoutes } from 'hono/dev' 8 | ``` 9 | 10 | ## `getRouterName()` 11 | 12 | You can get the name of the currently used router with `getRouterName()`. 13 | 14 | ```ts 15 | const app = new Hono() 16 | 17 | // ... 18 | 19 | console.log(getRouterName(app)) 20 | ``` 21 | 22 | ## `showRoutes()` 23 | 24 | `showRoutes()` function displays the registered routes in your console. 25 | 26 | Consider an application like the following: 27 | 28 | ```ts 29 | const app = new Hono().basePath('/v1') 30 | 31 | app.get('/posts', (c) => { 32 | // ... 33 | }) 34 | 35 | app.get('/posts/:id', (c) => { 36 | // ... 37 | }) 38 | 39 | app.post('/posts', (c) => { 40 | // ... 41 | }) 42 | 43 | showRoutes(app, { 44 | verbose: true, 45 | }) 46 | ``` 47 | 48 | When this application starts running, the routes will be shown in your console as follows: 49 | 50 | ```txt 51 | GET /v1/posts 52 | GET /v1/posts/:id 53 | POST /v1/posts 54 | ``` 55 | 56 | ## Options 57 | 58 | ### verbose: `boolean` 59 | 60 | When set to `true`, it displays verbose information. 61 | 62 | ### colorize: `boolean` 63 | 64 | When set to `false`, the output will not be colored. 65 | -------------------------------------------------------------------------------- /docs/helpers/factory.md: -------------------------------------------------------------------------------- 1 | # Factory Helper 2 | 3 | The Factory Helper provides useful functions for creating Hono's components such as Middleware. Sometimes it's difficult to set the proper TypeScript types, but this helper facilitates that. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { createFactory, createMiddleware } from 'hono/factory' 10 | ``` 11 | 12 | ## `createFactory()` 13 | 14 | `createFactory()` will create an instance of the Factory class. 15 | 16 | ```ts 17 | import { createFactory } from 'hono/factory' 18 | 19 | const factory = createFactory() 20 | ``` 21 | 22 | You can pass your Env types as Generics: 23 | 24 | ```ts 25 | type Env = { 26 | Variables: { 27 | foo: string 28 | } 29 | } 30 | 31 | const factory = createFactory() 32 | ``` 33 | 34 | ### Options 35 | 36 | ### defaultAppOptions: `HonoOptions` 37 | 38 | The default options to pass to the Hono application created by `createApp()`. 39 | 40 | ```ts 41 | const factory = createFactory({ 42 | defaultAppOptions: { strict: false }, 43 | }) 44 | 45 | const app = factory.createApp() // `strict: false` is applied 46 | ``` 47 | 48 | ## `createMiddleware()` 49 | 50 | `createMiddleware()` is shortcut of `factory.createMiddleware()`. 51 | This function will create your custom middleware. 52 | 53 | ```ts 54 | const messageMiddleware = createMiddleware(async (c, next) => { 55 | await next() 56 | c.res.headers.set('X-Message', 'Good morning!') 57 | }) 58 | ``` 59 | 60 | Tip: If you want to get an argument like `message`, you can create it as a function like the following. 61 | 62 | ```ts 63 | const messageMiddleware = (message: string) => { 64 | return createMiddleware(async (c, next) => { 65 | await next() 66 | c.res.headers.set('X-Message', message) 67 | }) 68 | } 69 | 70 | app.use(messageMiddleware('Good evening!')) 71 | ``` 72 | 73 | ## `factory.createHandlers()` 74 | 75 | `createHandlers()` helps to define handlers in a different place than `app.get('/')`. 76 | 77 | ```ts 78 | import { createFactory } from 'hono/factory' 79 | import { logger } from 'hono/logger' 80 | 81 | // ... 82 | 83 | const factory = createFactory() 84 | 85 | const middleware = factory.createMiddleware(async (c, next) => { 86 | c.set('foo', 'bar') 87 | await next() 88 | }) 89 | 90 | const handlers = factory.createHandlers(logger(), middleware, (c) => { 91 | return c.json(c.var.foo) 92 | }) 93 | 94 | app.get('/api', ...handlers) 95 | ``` 96 | 97 | ## `factory.createApp()` 98 | 99 | `createApp()` helps to create an instance of Hono with the proper types. If you use this method with `createFactory()`, you can avoid redundancy in the definition of the `Env` type. 100 | 101 | If your application is like this, you have to set the `Env` in two places: 102 | 103 | ```ts 104 | import { createMiddleware } from 'hono/factory' 105 | 106 | type Env = { 107 | Variables: { 108 | myVar: string 109 | } 110 | } 111 | 112 | // 1. Set the `Env` to `new Hono()` 113 | const app = new Hono() 114 | 115 | // 2. Set the `Env` to `createMiddleware()` 116 | const mw = createMiddleware(async (c, next) => { 117 | await next() 118 | }) 119 | 120 | app.use(mw) 121 | ``` 122 | 123 | By using `createFactory()` and `createApp()`, you can set the `Env` only in one place. 124 | 125 | ```ts 126 | import { createFactory } from 'hono/factory' 127 | 128 | // ... 129 | 130 | // Set the `Env` to `createFactory()` 131 | const factory = createFactory() 132 | 133 | const app = factory.createApp() 134 | 135 | // factory also has `createMiddleware()` 136 | const mw = factory.createMiddleware(async (c, next) => { 137 | await next() 138 | }) 139 | ``` 140 | 141 | `createFactory()` can receive the `initApp` option to initialize an `app` created by `createApp()`. The following is an example that uses the option. 142 | 143 | ```ts 144 | // factory-with-db.ts 145 | type Env = { 146 | Bindings: { 147 | MY_DB: D1Database 148 | } 149 | Variables: { 150 | db: DrizzleD1Database 151 | } 152 | } 153 | 154 | export default createFactory({ 155 | initApp: (app) => { 156 | app.use(async (c, next) => { 157 | const db = drizzle(c.env.MY_DB) 158 | c.set('db', db) 159 | await next() 160 | }) 161 | }, 162 | }) 163 | ``` 164 | 165 | ```ts 166 | // crud.ts 167 | import factoryWithDB from './factory-with-db' 168 | 169 | const app = factoryWithDB.createApp() 170 | 171 | app.post('/posts', (c) => { 172 | c.var.db.insert() 173 | // ... 174 | }) 175 | ``` 176 | -------------------------------------------------------------------------------- /docs/helpers/html.md: -------------------------------------------------------------------------------- 1 | # html Helper 2 | 3 | The html Helper lets you write HTML in JavaScript template literal with a tag named `html`. Using `raw()`, the content will be rendered as is. You have to escape these strings by yourself. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { html, raw } from 'hono/html' 10 | ``` 11 | 12 | ## `html` 13 | 14 | ```ts 15 | const app = new Hono() 16 | 17 | app.get('/:username', (c) => { 18 | const { username } = c.req.param() 19 | return c.html( 20 | html` 21 |

Hello! ${username}!

` 22 | ) 23 | }) 24 | ``` 25 | 26 | ### Insert snippets into JSX 27 | 28 | Insert the inline script into JSX: 29 | 30 | ```tsx 31 | app.get('/', (c) => { 32 | return c.html( 33 | 34 | 35 | Test Site 36 | {html` 37 | 41 | `} 42 | 43 | Hello! 44 | 45 | ) 46 | }) 47 | ``` 48 | 49 | ### Act as functional component 50 | 51 | Since `html` returns an HtmlEscapedString, it can act as a fully functional component without using JSX. 52 | 53 | #### Use `html` to speed up the process instead of `memo` 54 | 55 | ```typescript 56 | const Footer = () => html` 57 |
58 |
My Address...
59 |
60 | ` 61 | ``` 62 | 63 | ### Receives props and embeds values 64 | 65 | ```typescript 66 | interface SiteData { 67 | title: string 68 | description: string 69 | image: string 70 | children?: any 71 | } 72 | const Layout = (props: SiteData) => html` 73 | 74 | 75 | 76 | ${props.title} 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | ${props.children} 86 | 87 | 88 | ` 89 | 90 | const Content = (props: { siteData: SiteData; name: string }) => ( 91 | 92 |

Hello {props.name}

93 |
94 | ) 95 | 96 | app.get('/', (c) => { 97 | const props = { 98 | name: 'World', 99 | siteData: { 100 | title: 'Hello <> World', 101 | description: 'This is a description', 102 | image: 'https://example.com/image.png', 103 | }, 104 | } 105 | return c.html() 106 | }) 107 | ``` 108 | 109 | ## `raw()` 110 | 111 | ```ts 112 | app.get('/', (c) => { 113 | const name = 'John "Johnny" Smith' 114 | return c.html(html`

I'm ${raw(name)}.

`) 115 | }) 116 | ``` 117 | 118 | ## Tips 119 | 120 | Thanks to these libraries, Visual Studio Code and vim also interprets template literals as HTML, allowing syntax highlighting and formatting to be applied. 121 | 122 | - 123 | - 124 | -------------------------------------------------------------------------------- /docs/helpers/proxy.md: -------------------------------------------------------------------------------- 1 | # Proxy Helper 2 | 3 | Proxy Helper provides useful functions when using Hono application as a (reverse) proxy. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { proxy } from 'hono/proxy' 10 | ``` 11 | 12 | ## `proxy()` 13 | 14 | `proxy()` is a `fetch()` API wrapper for proxy. The parameters and return value are the same as for `fetch()` (except for the proxy-specific options). 15 | 16 | The `Accept-Encoding` header is replaced with an encoding that the current runtime can handle. Unnecessary response headers are deleted, and a `Response` object is returned that you can return as a response from the handler. 17 | 18 | ### Examples 19 | 20 | Simple usage: 21 | 22 | ```ts 23 | app.get('/proxy/:path', (c) => { 24 | return proxy(`http://${originServer}/${c.req.param('path')}`) 25 | }) 26 | ``` 27 | 28 | Complicated usage: 29 | 30 | ```ts 31 | app.get('/proxy/:path', async (c) => { 32 | const res = await proxy( 33 | `http://${originServer}/${c.req.param('path')}`, 34 | { 35 | headers: { 36 | ...c.req.header(), // optional, specify only when forwarding all the request data (including credentials) is necessary. 37 | 'X-Forwarded-For': '127.0.0.1', 38 | 'X-Forwarded-Host': c.req.header('host'), 39 | Authorization: undefined, // do not propagate request headers contained in c.req.header('Authorization') 40 | }, 41 | } 42 | ) 43 | res.headers.delete('Set-Cookie') 44 | return res 45 | }) 46 | ``` 47 | 48 | Or you can pass the `c.req` as a parameter. 49 | 50 | ```ts 51 | app.all('/proxy/:path', (c) => { 52 | return proxy(`http://${originServer}/${c.req.param('path')}`, { 53 | ...c.req, // optional, specify only when forwarding all the request data (including credentials) is necessary. 54 | headers: { 55 | ...c.req.header(), 56 | 'X-Forwarded-For': '127.0.0.1', 57 | 'X-Forwarded-Host': c.req.header('host'), 58 | Authorization: undefined, // do not propagate request headers contained in c.req.header('Authorization') 59 | }, 60 | }) 61 | }) 62 | ``` 63 | 64 | ### `ProxyFetch` 65 | 66 | The type of `proxy()` is defined as `ProxyFetch` and is as follows 67 | 68 | ```ts 69 | interface ProxyRequestInit extends Omit { 70 | raw?: Request 71 | headers?: 72 | | HeadersInit 73 | | [string, string][] 74 | | Record 75 | | Record 76 | } 77 | 78 | interface ProxyFetch { 79 | ( 80 | input: string | URL | Request, 81 | init?: ProxyRequestInit 82 | ): Promise 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /docs/helpers/streaming.md: -------------------------------------------------------------------------------- 1 | # Streaming Helper 2 | 3 | The Streaming Helper provides methods for streaming responses. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { stream, streamText, streamSSE } from 'hono/streaming' 10 | ``` 11 | 12 | ## `stream()` 13 | 14 | It returns a simple streaming response as `Response` object. 15 | 16 | ```ts 17 | app.get('/stream', (c) => { 18 | return stream(c, async (stream) => { 19 | // Write a process to be executed when aborted. 20 | stream.onAbort(() => { 21 | console.log('Aborted!') 22 | }) 23 | // Write a Uint8Array. 24 | await stream.write(new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f])) 25 | // Pipe a readable stream. 26 | await stream.pipe(anotherReadableStream) 27 | }) 28 | }) 29 | ``` 30 | 31 | ## `streamText()` 32 | 33 | It returns a streaming response with `Content-Type:text/plain`, `Transfer-Encoding:chunked`, and `X-Content-Type-Options:nosniff` headers. 34 | 35 | ```ts 36 | app.get('/streamText', (c) => { 37 | return streamText(c, async (stream) => { 38 | // Write a text with a new line ('\n'). 39 | await stream.writeln('Hello') 40 | // Wait 1 second. 41 | await stream.sleep(1000) 42 | // Write a text without a new line. 43 | await stream.write(`Hono!`) 44 | }) 45 | }) 46 | ``` 47 | 48 | ::: warning 49 | 50 | 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. 51 | 52 | ```ts 53 | app.get('/streamText', (c) => { 54 | c.header('Content-Encoding', 'Identity') 55 | return streamText(c, async (stream) => { 56 | // ... 57 | }) 58 | }) 59 | ``` 60 | 61 | ::: 62 | 63 | ## `streamSSE()` 64 | 65 | It allows you to stream Server-Sent Events (SSE) seamlessly. 66 | 67 | ```ts 68 | const app = new Hono() 69 | let id = 0 70 | 71 | app.get('/sse', async (c) => { 72 | return streamSSE(c, async (stream) => { 73 | while (true) { 74 | const message = `It is ${new Date().toISOString()}` 75 | await stream.writeSSE({ 76 | data: message, 77 | event: 'time-update', 78 | id: String(id++), 79 | }) 80 | await stream.sleep(1000) 81 | } 82 | }) 83 | }) 84 | ``` 85 | 86 | ## Error Handling 87 | 88 | The third argument of the streaming helper is an error handler. 89 | This argument is optional, if you don't specify it, the error will be output as a console error. 90 | 91 | ```ts 92 | app.get('/stream', (c) => { 93 | return stream( 94 | c, 95 | async (stream) => { 96 | // Write a process to be executed when aborted. 97 | stream.onAbort(() => { 98 | console.log('Aborted!') 99 | }) 100 | // Write a Uint8Array. 101 | await stream.write( 102 | new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]) 103 | ) 104 | // Pipe a readable stream. 105 | await stream.pipe(anotherReadableStream) 106 | }, 107 | (err, stream) => { 108 | stream.writeln('An error occurred!') 109 | console.error(err) 110 | } 111 | ) 112 | }) 113 | ``` 114 | 115 | The stream will be automatically closed after the callbacks are executed. 116 | 117 | ::: warning 118 | 119 | If the callback function of the streaming helper throws an error, the `onError` event of Hono will not be triggered. 120 | 121 | `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. 122 | 123 | ::: 124 | -------------------------------------------------------------------------------- /docs/helpers/testing.md: -------------------------------------------------------------------------------- 1 | # Testing Helper 2 | 3 | The Testing Helper provides functions to make testing of Hono applications easier. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { testClient } from 'hono/testing' 10 | ``` 11 | 12 | ## `testClient()` 13 | 14 | The `testClient()` function takes an instance of Hono as its first argument and returns an object typed according to your Hono application's routes, similar to the [Hono Client](/docs/guides/rpc#client). This allows you to call your defined routes in a type-safe manner with editor autocompletion within your tests. 15 | 16 | **Important Note on Type Inference:** 17 | 18 | For the `testClient` to correctly infer the types of your routes and provide autocompletion, **you must define your routes using chained methods directly on the `Hono` instance**. 19 | 20 | The type inference relies on the type flowing through the chained `.get()`, `.post()`, etc., calls. If you define routes separately after creating the Hono instance (like the common pattern shown in the "Hello World" example: `const app = new Hono(); app.get(...)`), the `testClient` will not have the necessary type information for specific routes, and you won't get the type-safe client features. 21 | 22 | **Example:** 23 | 24 | This example works because the `.get()` method is chained directly onto the `new Hono()` call: 25 | 26 | ```ts 27 | // index.ts 28 | const app = new Hono().get('/search', (c) => { 29 | const query = c.req.query('q') 30 | return c.json({ query: query, results: ['result1', 'result2'] }) 31 | }) 32 | 33 | export default app 34 | ``` 35 | 36 | ```ts 37 | // index.test.ts 38 | import { Hono } from 'hono' 39 | import { testClient } from 'hono/testing' 40 | import { describe, it, expect } from 'vitest' // Or your preferred test runner 41 | import app from './app' 42 | 43 | describe('Search Endpoint', () => { 44 | // Create the test client from the app instance 45 | const client = testClient(app) 46 | 47 | it('should return search results', async () => { 48 | // Call the endpoint using the typed client 49 | // Notice the type safety for query parameters (if defined in the route) 50 | // and the direct access via .$get() 51 | const res = await client.search.$get({ 52 | query: { q: 'hono' }, 53 | }) 54 | 55 | // Assertions 56 | expect(res.status).toBe(200) 57 | expect(await res.json()).toEqual({ 58 | query: 'hono', 59 | results: ['result1', 'result2'], 60 | }) 61 | }) 62 | }) 63 | ``` 64 | 65 | To include headers in your test, pass them as the second parameter in the call. 66 | 67 | ```ts 68 | // index.test.ts 69 | import { Hono } from 'hono' 70 | import { testClient } from 'hono/testing' 71 | import { describe, it, expect } from 'vitest' // Or your preferred test runner 72 | import app from './app' 73 | 74 | describe('Search Endpoint', () => { 75 | // Create the test client from the app instance 76 | const client = testClient(app) 77 | 78 | it('should return search results', async () => { 79 | // Include the token in the headers and set the content type 80 | const token = 'this-is-a-very-clean-token' 81 | const res = await client.search.$get( 82 | { 83 | query: { q: 'hono' }, 84 | }, 85 | { 86 | headers: { 87 | Authorization: `Bearer ${token}`, 88 | 'Content-Type': `application/json`, 89 | }, 90 | } 91 | ) 92 | 93 | // Assertions 94 | expect(res.status).toBe(200) 95 | expect(await res.json()).toEqual({ 96 | query: 'hono', 97 | results: ['result1', 'result2'], 98 | }) 99 | }) 100 | }) 101 | ``` 102 | -------------------------------------------------------------------------------- /docs/helpers/websocket.md: -------------------------------------------------------------------------------- 1 | # WebSocket Helper 2 | 3 | WebSocket Helper is a helper for server-side WebSockets in Hono applications. 4 | Currently Cloudflare Workers / Pages, Deno, and Bun adapters are available. 5 | 6 | ## Import 7 | 8 | ::: code-group 9 | 10 | ```ts [Cloudflare Workers] 11 | import { Hono } from 'hono' 12 | import { upgradeWebSocket } from 'hono/cloudflare-workers' 13 | ``` 14 | 15 | ```ts [Deno] 16 | import { Hono } from 'hono' 17 | import { upgradeWebSocket } from 'hono/deno' 18 | ``` 19 | 20 | ```ts [Bun] 21 | import { Hono } from 'hono' 22 | import { createBunWebSocket } from 'hono/bun' 23 | import type { ServerWebSocket } from 'bun' 24 | 25 | const { upgradeWebSocket, websocket } = 26 | createBunWebSocket() 27 | 28 | // ... 29 | 30 | export default { 31 | fetch: app.fetch, 32 | websocket, 33 | } 34 | ``` 35 | 36 | ::: 37 | 38 | If you use Node.js, you can use [@hono/node-ws](https://github.com/honojs/middleware/tree/main/packages/node-ws). 39 | 40 | ## `upgradeWebSocket()` 41 | 42 | `upgradeWebSocket()` returns a handler for handling WebSocket. 43 | 44 | ```ts 45 | const app = new Hono() 46 | 47 | app.get( 48 | '/ws', 49 | upgradeWebSocket((c) => { 50 | return { 51 | onMessage(event, ws) { 52 | console.log(`Message from client: ${event.data}`) 53 | ws.send('Hello from server!') 54 | }, 55 | onClose: () => { 56 | console.log('Connection closed') 57 | }, 58 | } 59 | }) 60 | ) 61 | ``` 62 | 63 | Available events: 64 | 65 | - `onOpen` - Currently, Cloudflare Workers does not support it. 66 | - `onMessage` 67 | - `onClose` 68 | - `onError` 69 | 70 | ::: warning 71 | 72 | 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. 73 | 74 | Therefore, please be cautious if you are using WebSocket Helper and middleware at the same time. 75 | 76 | ::: 77 | 78 | ## RPC-mode 79 | 80 | Handlers defined with WebSocket Helper support RPC mode. 81 | 82 | ```ts 83 | // server.ts 84 | const wsApp = app.get( 85 | '/ws', 86 | upgradeWebSocket((c) => { 87 | //... 88 | }) 89 | ) 90 | 91 | export type WebSocketApp = typeof wsApp 92 | 93 | // client.ts 94 | const client = hc('http://localhost:8787') 95 | const socket = client.ws.$ws() // A WebSocket object for a client 96 | ``` 97 | 98 | ## Examples 99 | 100 | See the examples using WebSocket Helper. 101 | 102 | ### Server and Client 103 | 104 | ```ts 105 | // server.ts 106 | import { Hono } from 'hono' 107 | import { upgradeWebSocket } from 'hono/cloudflare-workers' 108 | 109 | const app = new Hono().get( 110 | '/ws', 111 | upgradeWebSocket(() => { 112 | return { 113 | onMessage: (event) => { 114 | console.log(event.data) 115 | }, 116 | } 117 | }) 118 | ) 119 | 120 | export default app 121 | ``` 122 | 123 | ```ts 124 | // client.ts 125 | import { hc } from 'hono/client' 126 | import type app from './server' 127 | 128 | const client = hc('http://localhost:8787') 129 | const ws = client.ws.$ws(0) 130 | 131 | ws.addEventListener('open', () => { 132 | setInterval(() => { 133 | ws.send(new Date().toString()) 134 | }, 1000) 135 | }) 136 | ``` 137 | 138 | ### Bun with JSX 139 | 140 | ```tsx 141 | import { Hono } from 'hono' 142 | import { createBunWebSocket } from 'hono/bun' 143 | import { html } from 'hono/html' 144 | 145 | const { upgradeWebSocket, websocket } = createBunWebSocket() 146 | 147 | const app = new Hono() 148 | 149 | app.get('/', (c) => { 150 | return c.html( 151 | 152 | 153 | 154 | 155 | 156 |
157 | {html` 158 | 165 | `} 166 | 167 | 168 | ) 169 | }) 170 | 171 | const ws = app.get( 172 | '/ws', 173 | upgradeWebSocket((c) => { 174 | let intervalId 175 | return { 176 | onOpen(_event, ws) { 177 | intervalId = setInterval(() => { 178 | ws.send(new Date().toString()) 179 | }, 200) 180 | }, 181 | onClose() { 182 | clearInterval(intervalId) 183 | }, 184 | } 185 | }) 186 | ) 187 | 188 | export default { 189 | fetch: app.fetch, 190 | websocket, 191 | } 192 | ``` 193 | -------------------------------------------------------------------------------- /docs/middleware/builtin/basic-auth.md: -------------------------------------------------------------------------------- 1 | # Basic Auth Middleware 2 | 3 | This middleware can apply Basic authentication to a specified path. 4 | Implementing Basic authentication with Cloudflare Workers or other platforms is more complicated than it seems, but with this middleware, it's a breeze. 5 | 6 | For more information about how the Basic auth scheme works under the hood, see the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme). 7 | 8 | ## Import 9 | 10 | ```ts 11 | import { Hono } from 'hono' 12 | import { basicAuth } from 'hono/basic-auth' 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```ts 18 | const app = new Hono() 19 | 20 | app.use( 21 | '/auth/*', 22 | basicAuth({ 23 | username: 'hono', 24 | password: 'acoolproject', 25 | }) 26 | ) 27 | 28 | app.get('/auth/page', (c) => { 29 | return c.text('You are authorized') 30 | }) 31 | ``` 32 | 33 | To restrict to a specific route + method: 34 | 35 | ```ts 36 | const app = new Hono() 37 | 38 | app.get('/auth/page', (c) => { 39 | return c.text('Viewing page') 40 | }) 41 | 42 | app.delete( 43 | '/auth/page', 44 | basicAuth({ username: 'hono', password: 'acoolproject' }), 45 | (c) => { 46 | return c.text('Page deleted') 47 | } 48 | ) 49 | ``` 50 | 51 | If you want to verify the user by yourself, specify the `verifyUser` option; returning `true` means it is accepted. 52 | 53 | ```ts 54 | const app = new Hono() 55 | 56 | app.use( 57 | basicAuth({ 58 | verifyUser: (username, password, c) => { 59 | return ( 60 | username === 'dynamic-user' && password === 'hono-password' 61 | ) 62 | }, 63 | }) 64 | ) 65 | ``` 66 | 67 | ## Options 68 | 69 | ### username: `string` 70 | 71 | The username of the user who is authenticating. 72 | 73 | ### password: `string` 74 | 75 | The password value for the provided username to authenticate against. 76 | 77 | ### realm: `string` 78 | 79 | The domain name of the realm, as part of the returned WWW-Authenticate challenge header. The default is `"Secure Area"`. 80 | See more: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives 81 | 82 | ### hashFunction: `Function` 83 | 84 | A function to handle hashing for safe comparison of passwords. 85 | 86 | ### verifyUser: `(username: string, password: string, c: Context) => boolean | Promise` 87 | 88 | The function to verify the user. 89 | 90 | ### invalidUserMessage: `string | object | MessageFunction` 91 | 92 | `MessageFunction` is `(c: Context) => string | object | Promise`. The custom message if the user is invalid. 93 | 94 | ## More Options 95 | 96 | ### ...users: `{ username: string, password: string }[]` 97 | 98 | ## Recipes 99 | 100 | ### Defining Multiple Users 101 | 102 | This middleware also allows you to pass arbitrary parameters containing objects defining more `username` and `password` pairs. 103 | 104 | ```ts 105 | app.use( 106 | '/auth/*', 107 | basicAuth( 108 | { 109 | username: 'hono', 110 | password: 'acoolproject', 111 | // Define other params in the first object 112 | realm: 'www.example.com', 113 | }, 114 | { 115 | username: 'hono-admin', 116 | password: 'super-secure', 117 | // Cannot redefine other params here 118 | }, 119 | { 120 | username: 'hono-user-1', 121 | password: 'a-secret', 122 | // Or here 123 | } 124 | ) 125 | ) 126 | ``` 127 | 128 | Or less hardcoded: 129 | 130 | ```ts 131 | import { users } from '../config/users' 132 | 133 | app.use( 134 | '/auth/*', 135 | basicAuth( 136 | { 137 | realm: 'www.example.com', 138 | ...users[0], 139 | }, 140 | ...users.slice(1) 141 | ) 142 | ) 143 | ``` 144 | -------------------------------------------------------------------------------- /docs/middleware/builtin/bearer-auth.md: -------------------------------------------------------------------------------- 1 | # Bearer Auth Middleware 2 | 3 | The Bearer Auth Middleware provides authentication by verifying an API token in the Request header. 4 | The HTTP clients accessing the endpoint will add the `Authorization` header with `Bearer {token}` as the header value. 5 | 6 | Using `curl` from the terminal, it would look like this: 7 | 8 | ```sh 9 | curl -H 'Authorization: Bearer honoiscool' http://localhost:8787/auth/page 10 | ``` 11 | 12 | ## Import 13 | 14 | ```ts 15 | import { Hono } from 'hono' 16 | import { bearerAuth } from 'hono/bearer-auth' 17 | ``` 18 | 19 | ## Usage 20 | 21 | > [!NOTE] 22 | > Your `token` must match the regex `/[A-Za-z0-9._~+/-]+=*/`, otherwise a 400 error will be returned. Notably, this regex acommodates both URL-safe Base64- and standard Base64-encoded JWTs. This middleware does not require the bearer token to be a JWT, just that it matches the above regex. 23 | 24 | ```ts 25 | const app = new Hono() 26 | 27 | const token = 'honoiscool' 28 | 29 | app.use('/api/*', bearerAuth({ token })) 30 | 31 | app.get('/api/page', (c) => { 32 | return c.json({ message: 'You are authorized' }) 33 | }) 34 | ``` 35 | 36 | To restrict to a specific route + method: 37 | 38 | ```ts 39 | const app = new Hono() 40 | 41 | const token = 'honoiscool' 42 | 43 | app.get('/api/page', (c) => { 44 | return c.json({ message: 'Read posts' }) 45 | }) 46 | 47 | app.post('/api/page', bearerAuth({ token }), (c) => { 48 | return c.json({ message: 'Created post!' }, 201) 49 | }) 50 | ``` 51 | 52 | To implement multiple tokens (E.g., any valid token can read but create/update/delete are restricted to a privileged token): 53 | 54 | ```ts 55 | const app = new Hono() 56 | 57 | const readToken = 'read' 58 | const privilegedToken = 'read+write' 59 | const privilegedMethods = ['POST', 'PUT', 'PATCH', 'DELETE'] 60 | 61 | app.on('GET', '/api/page/*', async (c, next) => { 62 | // List of valid tokens 63 | const bearer = bearerAuth({ token: [readToken, privilegedToken] }) 64 | return bearer(c, next) 65 | }) 66 | app.on(privilegedMethods, '/api/page/*', async (c, next) => { 67 | // Single valid privileged token 68 | const bearer = bearerAuth({ token: privilegedToken }) 69 | return bearer(c, next) 70 | }) 71 | 72 | // Define handlers for GET, POST, etc. 73 | ``` 74 | 75 | If you want to verify the value of the token yourself, specify the `verifyToken` option; returning `true` means it is accepted. 76 | 77 | ```ts 78 | const app = new Hono() 79 | 80 | app.use( 81 | '/auth-verify-token/*', 82 | bearerAuth({ 83 | verifyToken: async (token, c) => { 84 | return token === 'dynamic-token' 85 | }, 86 | }) 87 | ) 88 | ``` 89 | 90 | ## Options 91 | 92 | ### token: `string` | `string[]` 93 | 94 | The string to validate the incoming bearer token against. 95 | 96 | ### realm: `string` 97 | 98 | The domain name of the realm, as part of the returned WWW-Authenticate challenge header. The default is `""`. 99 | See more: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives 100 | 101 | ### prefix: `string` 102 | 103 | The prefix (or known as `schema`) for the Authorization header value. The default is `"Bearer"`. 104 | 105 | ### headerName: `string` 106 | 107 | The header name. The default value is `Authorization`. 108 | 109 | ### hashFunction: `Function` 110 | 111 | A function to handle hashing for safe comparison of authentication tokens. 112 | 113 | ### verifyToken: `(token: string, c: Context) => boolean | Promise` 114 | 115 | The function to verify the token. 116 | 117 | ### noAuthenticationHeaderMessage: `string | object | MessageFunction` 118 | 119 | `MessageFunction` is `(c: Context) => string | object | Promise`. The custom message if it does not have an authentication header. 120 | 121 | ### invalidAuthenticationHeaderMessage: `string | object | MessageFunction` 122 | 123 | The custom message if the authentication header is invalid. 124 | 125 | ### invalidTokenMessage: `string | object | MessageFunction` 126 | 127 | The custom message if the token is invalid. 128 | -------------------------------------------------------------------------------- /docs/middleware/builtin/body-limit.md: -------------------------------------------------------------------------------- 1 | # Body Limit Middleware 2 | 3 | The Body Limit Middleware can limit the file size of the request body. 4 | 5 | This middleware first uses the value of the `Content-Length` header in the request, if present. 6 | If it is not set, it reads the body in the stream and executes an error handler if it is larger than the specified file size. 7 | 8 | ## Import 9 | 10 | ```ts 11 | import { Hono } from 'hono' 12 | import { bodyLimit } from 'hono/body-limit' 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```ts 18 | const app = new Hono() 19 | 20 | app.post( 21 | '/upload', 22 | bodyLimit({ 23 | maxSize: 50 * 1024, // 50kb 24 | onError: (c) => { 25 | return c.text('overflow :(', 413) 26 | }, 27 | }), 28 | async (c) => { 29 | const body = await c.req.parseBody() 30 | if (body['file'] instanceof File) { 31 | console.log(`Got file sized: ${body['file'].size}`) 32 | } 33 | return c.text('pass :)') 34 | } 35 | ) 36 | ``` 37 | 38 | ## Options 39 | 40 | ### maxSize: `number` 41 | 42 | The maximum file size of the file you want to limit. The default is `100 * 1024` - `100kb`. 43 | 44 | ### onError: `OnError` 45 | 46 | The error handler to be invoked if the specified file size is exceeded. 47 | 48 | ## Usage with Bun for large requests 49 | 50 | If the Body Limit Middleware is used explicitly to allow a request body larger than the default, it might be necessary to make changes to your `Bun.serve` configuration accordingly. [At the time of writing](https://github.com/oven-sh/bun/blob/f2cfa15e4ef9d730fc6842ad8b79fb7ab4c71cb9/packages/bun-types/bun.d.ts#L2191), `Bun.serve`'s default request body limit is 128MiB. If you set Hono's Body Limit Middleware to a value bigger than that, your requests will still fail and, additionally, the `onError` handler specified in the middleware will not be called. This is because `Bun.serve()` will set the status code to `413` and terminate the connection before passing the request to Hono. 51 | 52 | If you want to accept requests larger than 128MiB with Hono and Bun, you need to set the limit for Bun as well: 53 | 54 | ```ts 55 | export default { 56 | port: process.env['PORT'] || 3000, 57 | fetch: app.fetch, 58 | maxRequestBodySize: 1024 * 1024 * 200, // your value here 59 | } 60 | ``` 61 | 62 | or, depending on your setup: 63 | 64 | ```ts 65 | Bun.serve({ 66 | fetch(req, server) { 67 | return app.fetch(req, { ip: server.requestIP(req) }) 68 | }, 69 | maxRequestBodySize: 1024 * 1024 * 200, // your value here 70 | }) 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/middleware/builtin/cache.md: -------------------------------------------------------------------------------- 1 | # Cache Middleware 2 | 3 | The Cache middleware uses the Web Standards' [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache). 4 | 5 | The Cache middleware currently supports Cloudflare Workers projects using custom domains and Deno projects using [Deno 1.26+](https://github.com/denoland/deno/releases/tag/v1.26.0). Also available with Deno Deploy. 6 | 7 | Cloudflare Workers respects the `Cache-Control` header and return cached responses. For details, refer to [Cache on Cloudflare Docs](https://developers.cloudflare.com/workers/runtime-apis/cache/). Deno does not respect headers, so if you need to update the cache, you will need to implement your own mechanism. 8 | 9 | See [Usage](#usage) below for instructions on each platform. 10 | 11 | ## Import 12 | 13 | ```ts 14 | import { Hono } from 'hono' 15 | import { cache } from 'hono/cache' 16 | ``` 17 | 18 | ## Usage 19 | 20 | ::: code-group 21 | 22 | ```ts [Cloudflare Workers] 23 | app.get( 24 | '*', 25 | cache({ 26 | cacheName: 'my-app', 27 | cacheControl: 'max-age=3600', 28 | }) 29 | ) 30 | ``` 31 | 32 | ```ts [Deno] 33 | // Must use `wait: true` for the Deno runtime 34 | app.get( 35 | '*', 36 | cache({ 37 | cacheName: 'my-app', 38 | cacheControl: 'max-age=3600', 39 | wait: true, 40 | }) 41 | ) 42 | ``` 43 | 44 | ::: 45 | 46 | ## Options 47 | 48 | ### cacheName: `string` | `(c: Context) => string` | `Promise` 49 | 50 | The name of the cache. Can be used to store multiple caches with different identifiers. 51 | 52 | ### wait: `boolean` 53 | 54 | A boolean indicating if Hono should wait for the Promise of the `cache.put` function to resolve before continuing with the request. _Required to be true for the Deno environment_. The default is `false`. 55 | 56 | ### cacheControl: `string` 57 | 58 | A string of directives for the `Cache-Control` header. See the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) for more information. When this option is not provided, no `Cache-Control` header is added to requests. 59 | 60 | ### vary: `string` | `string[]` 61 | 62 | Sets the `Vary` header in the response. If the original response header already contains a `Vary` header, the values are merged, removing any duplicates. Setting this to `*` will result in an error. For more details on the Vary header and its implications for caching strategies, refer to the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary). 63 | 64 | ### keyGenerator: `(c: Context) => string | Promise` 65 | 66 | Generates keys for every request in the `cacheName` store. This can be used to cache data based on request parameters or context parameters. The default is `c.req.url`. 67 | -------------------------------------------------------------------------------- /docs/middleware/builtin/combine.md: -------------------------------------------------------------------------------- 1 | # Combine Middleware 2 | 3 | Combine Middleware combines multiple middleware functions into a single middleware. It provides three functions: 4 | 5 | - `some` - Runs only one of the given middleware. 6 | - `every` - Runs all given middleware. 7 | - `except` - Runs all given middleware only if a condition is not met. 8 | 9 | ## Import 10 | 11 | ```ts 12 | import { Hono } from 'hono' 13 | import { some, every, except } from 'hono/combine' 14 | ``` 15 | 16 | ## Usage 17 | 18 | Here's an example of complex access control rules using Combine Middleware. 19 | 20 | ```ts 21 | import { Hono } from 'hono' 22 | import { bearerAuth } from 'hono/bearer-auth' 23 | import { getConnInfo } from 'hono/cloudflare-workers' 24 | import { every, some } from 'hono/combine' 25 | import { ipRestriction } from 'hono/ip-restriction' 26 | import { rateLimit } from '@/my-rate-limit' 27 | 28 | const app = new Hono() 29 | 30 | app.use( 31 | '*', 32 | some( 33 | every( 34 | ipRestriction(getConnInfo, { allowList: ['192.168.0.2'] }), 35 | bearerAuth({ token }) 36 | ), 37 | // If both conditions are met, rateLimit will not execute. 38 | rateLimit() 39 | ) 40 | ) 41 | 42 | app.get('/', (c) => c.text('Hello Hono!')) 43 | ``` 44 | 45 | ### some 46 | 47 | Runs the first middleware that returns true. Middleware is applied in order, and if any middleware exits successfully, subsequent middleware will not run. 48 | 49 | ```ts 50 | import { some } from 'hono/combine' 51 | import { bearerAuth } from 'hono/bearer-auth' 52 | import { myRateLimit } from '@/rate-limit' 53 | 54 | // If client has a valid token, skip rate limiting. 55 | // Otherwise, apply rate limiting. 56 | app.use( 57 | '/api/*', 58 | some(bearerAuth({ token }), myRateLimit({ limit: 100 })) 59 | ) 60 | ``` 61 | 62 | ### every 63 | 64 | Runs all middleware and stops if any of them fail. Middleware is applied in order, and if any middleware throws an error, subsequent middleware will not run. 65 | 66 | ```ts 67 | import { some, every } from 'hono/combine' 68 | import { bearerAuth } from 'hono/bearer-auth' 69 | import { myCheckLocalNetwork } from '@/check-local-network' 70 | import { myRateLimit } from '@/rate-limit' 71 | 72 | // If client is in local network, skip authentication and rate limiting. 73 | // Otherwise, apply authentication and rate limiting. 74 | app.use( 75 | '/api/*', 76 | some( 77 | myCheckLocalNetwork(), 78 | every(bearerAuth({ token }), myRateLimit({ limit: 100 })) 79 | ) 80 | ) 81 | ``` 82 | 83 | ### except 84 | 85 | Runs all middleware except when the condition is met. You can pass a string or function as the condition. If multiple targets need to be matched, pass them as an array. 86 | 87 | ```ts 88 | import { except } from 'hono/combine' 89 | import { bearerAuth } from 'hono/bearer-auth' 90 | 91 | // If client is accessing public API, skip authentication. 92 | // Otherwise, require a valid token. 93 | app.use('/api/*', except('/api/public/*', bearerAuth({ token }))) 94 | ``` 95 | -------------------------------------------------------------------------------- /docs/middleware/builtin/compress.md: -------------------------------------------------------------------------------- 1 | # Compress Middleware 2 | 3 | This middleware compresses the response body, according to `Accept-Encoding` request header. 4 | 5 | ::: info 6 | **Note**: On Cloudflare Workers and Deno Deploy, the response body will be compressed automatically, so there is no need to use this middleware. 7 | 8 | **Bun**: This middleware uses `CompressionStream` which is not yet supported in bun. 9 | ::: 10 | 11 | ## Import 12 | 13 | ```ts 14 | import { Hono } from 'hono' 15 | import { compress } from 'hono/compress' 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```ts 21 | const app = new Hono() 22 | 23 | app.use(compress()) 24 | ``` 25 | 26 | ## Options 27 | 28 | ### encoding: `'gzip'` | `'deflate'` 29 | 30 | The compression scheme to allow for response compression. Either `gzip` or `deflate`. If not defined, both are allowed and will be used based on the `Accept-Encoding` header. `gzip` is prioritized if this option is not provided and the client provides both in the `Accept-Encoding` header. 31 | 32 | ### threshold: `number` 33 | 34 | The minimum size in bytes to compress. Defaults to 1024 bytes. 35 | -------------------------------------------------------------------------------- /docs/middleware/builtin/context-storage.md: -------------------------------------------------------------------------------- 1 | # Context Storage Middleware 2 | 3 | The Context Storage Middleware stores the Hono `Context` in the `AsyncLocalStorage`, to make it globally accessible. 4 | 5 | ::: info 6 | **Note** This middleware uses `AsyncLocalStorage`. The runtime should support it. 7 | 8 | **Cloudflare Workers**: To enable `AsyncLocalStorage`, add the [`nodejs_compat` or `nodejs_als` flag](https://developers.cloudflare.com/workers/configuration/compatibility-dates/#nodejs-compatibility-flag) to your `wrangler.toml` file. 9 | ::: 10 | 11 | ## Import 12 | 13 | ```ts 14 | import { Hono } from 'hono' 15 | import { contextStorage, getContext } from 'hono/context-storage' 16 | ``` 17 | 18 | ## Usage 19 | 20 | The `getContext()` will return the current Context object if the `contextStorage()` is applied as a middleware. 21 | 22 | ```ts 23 | type Env = { 24 | Variables: { 25 | message: string 26 | } 27 | } 28 | 29 | const app = new Hono() 30 | 31 | app.use(contextStorage()) 32 | 33 | app.use(async (c, next) => { 34 | c.set('message', 'Hello!') 35 | await next() 36 | }) 37 | 38 | // You can access the variable outside the handler. 39 | const getMessage = () => { 40 | return getContext().var.message 41 | } 42 | 43 | app.get('/', (c) => { 44 | return c.text(getMessage()) 45 | }) 46 | ``` 47 | 48 | On Cloudflare Workers, you can access the bindings outside the handler. 49 | 50 | ```ts 51 | type Env = { 52 | Bindings: { 53 | KV: KVNamespace 54 | } 55 | } 56 | 57 | const app = new Hono() 58 | 59 | app.use(contextStorage()) 60 | 61 | const setKV = (value: string) => { 62 | return getContext().env.KV.put('key', value) 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/middleware/builtin/cors.md: -------------------------------------------------------------------------------- 1 | # CORS Middleware 2 | 3 | There are many use cases of Cloudflare Workers as Web APIs and calling them from external front-end application. 4 | For them we have to implement CORS, let's do this with middleware as well. 5 | 6 | ## Import 7 | 8 | ```ts 9 | import { Hono } from 'hono' 10 | import { cors } from 'hono/cors' 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```ts 16 | const app = new Hono() 17 | 18 | // CORS should be called before the route 19 | app.use('/api/*', cors()) 20 | app.use( 21 | '/api2/*', 22 | cors({ 23 | origin: 'http://example.com', 24 | allowHeaders: ['X-Custom-Header', 'Upgrade-Insecure-Requests'], 25 | allowMethods: ['POST', 'GET', 'OPTIONS'], 26 | exposeHeaders: ['Content-Length', 'X-Kuma-Revision'], 27 | maxAge: 600, 28 | credentials: true, 29 | }) 30 | ) 31 | 32 | app.all('/api/abc', (c) => { 33 | return c.json({ success: true }) 34 | }) 35 | app.all('/api2/abc', (c) => { 36 | return c.json({ success: true }) 37 | }) 38 | ``` 39 | 40 | Multiple origins: 41 | 42 | ```ts 43 | app.use( 44 | '/api3/*', 45 | cors({ 46 | origin: ['https://example.com', 'https://example.org'], 47 | }) 48 | ) 49 | 50 | // Or you can use "function" 51 | app.use( 52 | '/api4/*', 53 | cors({ 54 | // `c` is a `Context` object 55 | origin: (origin, c) => { 56 | return origin.endsWith('.example.com') 57 | ? origin 58 | : 'http://example.com' 59 | }, 60 | }) 61 | ) 62 | ``` 63 | 64 | ## Options 65 | 66 | ### origin: `string` | `string[]` | `(origin:string, c:Context) => string` 67 | 68 | The value of "_Access-Control-Allow-Origin_" CORS header. You can also pass the callback function like `origin: (origin) => (origin.endsWith('.example.com') ? origin : 'http://example.com')`. The default is `*`. 69 | 70 | ### allowMethods: `string[]` 71 | 72 | The value of "_Access-Control-Allow-Methods_" CORS header. The default is `['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']`. 73 | 74 | ### allowHeaders: `string[]` 75 | 76 | The value of "_Access-Control-Allow-Headers_" CORS header. The default is `[]`. 77 | 78 | ### maxAge: `number` 79 | 80 | The value of "_Access-Control-Max-Age_" CORS header. 81 | 82 | ### credentials: `boolean` 83 | 84 | The value of "_Access-Control-Allow-Credentials_" CORS header. 85 | 86 | ### exposeHeaders: `string[]` 87 | 88 | The value of "_Access-Control-Expose-Headers_" CORS header. The default is `[]`. 89 | 90 | ## Environment-dependent CORS configuration 91 | 92 | If you want to adjust CORS configuration according to the execution environment, such as development or production, injecting values from environment variables is convenient as it eliminates the need for the application to be aware of its own execution environment. See the example below for clarification. 93 | 94 | ```ts 95 | app.use('*', async (c, next) => { 96 | const corsMiddlewareHandler = cors({ 97 | origin: c.env.CORS_ORIGIN, 98 | }) 99 | return corsMiddlewareHandler(c, next) 100 | }) 101 | ``` 102 | -------------------------------------------------------------------------------- /docs/middleware/builtin/csrf.md: -------------------------------------------------------------------------------- 1 | # CSRF Protection 2 | 3 | CSRF Protection Middleware prevents CSRF attacks by checking request headers. 4 | 5 | This middleware protects against CSRF attacks such as submitting with a form element by comparing the value of the `Origin` header with the requested URL. 6 | 7 | Old browsers that do not send `Origin` headers, or environments that use reverse proxies to remove `Origin` headers, may not work well. In such environments, use the other CSRF token methods. 8 | 9 | ## Import 10 | 11 | ```ts 12 | import { Hono } from 'hono' 13 | import { csrf } from 'hono/csrf' 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```ts 19 | const app = new Hono() 20 | 21 | app.use(csrf()) 22 | 23 | // Specifying origins with using `origin` option 24 | // string 25 | app.use(csrf({ origin: 'myapp.example.com' })) 26 | 27 | // string[] 28 | app.use( 29 | csrf({ 30 | origin: ['myapp.example.com', 'development.myapp.example.com'], 31 | }) 32 | ) 33 | 34 | // Function 35 | // It is strongly recommended that the protocol be verified to ensure a match to `$`. 36 | // You should *never* do a forward match. 37 | app.use( 38 | '*', 39 | csrf({ 40 | origin: (origin) => 41 | /https:\/\/(\w+\.)?myapp\.example\.com$/.test(origin), 42 | }) 43 | ) 44 | ``` 45 | 46 | ## Options 47 | 48 | ### origin: `string` | `string[]` | `Function` 49 | 50 | Specify origins. 51 | -------------------------------------------------------------------------------- /docs/middleware/builtin/etag.md: -------------------------------------------------------------------------------- 1 | # ETag Middleware 2 | 3 | Using this middleware, you can add ETag headers easily. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { etag } from 'hono/etag' 10 | ``` 11 | 12 | ## Usage 13 | 14 | ```ts 15 | const app = new Hono() 16 | 17 | app.use('/etag/*', etag()) 18 | app.get('/etag/abc', (c) => { 19 | return c.text('Hono is cool') 20 | }) 21 | ``` 22 | 23 | ## The retained headers 24 | 25 | The 304 Response must include the headers that would have been sent in an equivalent 200 OK response. The default headers are Cache-Control, Content-Location, Date, ETag, Expires, and Vary. 26 | 27 | If you want to add the header that is sent, you can use `retainedHeaders` option and `RETAINED_304_HEADERS` strings array variable that includes the default headers: 28 | 29 | ```ts 30 | import { etag, RETAINED_304_HEADERS } from 'hono/etag' 31 | 32 | // ... 33 | 34 | app.use( 35 | '/etag/*', 36 | etag({ 37 | retainedHeaders: ['x-message', ...RETAINED_304_HEADERS], 38 | }) 39 | ) 40 | ``` 41 | 42 | ## Options 43 | 44 | ### weak: `boolean` 45 | 46 | Define using or not using a [weak validation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests#weak_validation). If `true` is set, then `w/` is added to the prefix of the value. The default is `false`. 47 | 48 | ### retainedHeaders: `string[]` 49 | 50 | The headers that you want to retain in the 304 Response. 51 | 52 | ### generateDigest: `(body: Uint8Array) => ArrayBuffer | Promise` 53 | 54 | A custom digest generation function. By default, it uses `SHA-1`. This function is called with the response body as a `Uint8Array` and should return a hash as an `ArrayBuffer` or a Promise of one. 55 | -------------------------------------------------------------------------------- /docs/middleware/builtin/ip-restriction.md: -------------------------------------------------------------------------------- 1 | # IP Restriction Middleware 2 | 3 | IP Restriction Middleware is middleware that limits access to resources based on the IP address of the user. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { ipRestriction } from 'hono/ip-restriction' 10 | ``` 11 | 12 | ## Usage 13 | 14 | For your application running on Bun, if you want to allow access only from local, you can write it as follows. Specify the rules you want to deny in the `denyList` and the rules you want to allow in the `allowList`. 15 | 16 | ```ts 17 | import { Hono } from 'hono' 18 | import { getConnInfo } from 'hono/bun' 19 | import { ipRestriction } from 'hono/ip-restriction' 20 | 21 | const app = new Hono() 22 | 23 | app.use( 24 | '*', 25 | ipRestriction(getConnInfo, { 26 | denyList: [], 27 | allowList: ['127.0.0.1', '::1'], 28 | }) 29 | ) 30 | 31 | app.get('/', (c) => c.text('Hello Hono!')) 32 | ``` 33 | 34 | Pass the `getConninfo` from the [ConnInfo helper](/docs/helpers/conninfo) appropriate for your environment as the first argument of `ipRestriction`. For example, for Deno, it would look like this: 35 | 36 | ```ts 37 | import { getConnInfo } from 'hono/deno' 38 | import { ipRestriction } from 'hono/ip-restriction' 39 | 40 | //... 41 | 42 | app.use( 43 | '*', 44 | ipRestriction(getConnInfo, { 45 | // ... 46 | }) 47 | ) 48 | ``` 49 | 50 | ## Rules 51 | 52 | Follow the instructions below for writing rules. 53 | 54 | ### IPv4 55 | 56 | - `192.168.2.0` - Static IP Address 57 | - `192.168.2.0/24` - CIDR Notation 58 | - `*` - ALL Addresses 59 | 60 | ### IPv6 61 | 62 | - `::1` - Static IP Address 63 | - `::1/10` - CIDR Notation 64 | - `*` - ALL Addresses 65 | 66 | ## Error handling 67 | 68 | To customize the error, return a `Response` in the third argument. 69 | 70 | ```ts 71 | app.use( 72 | '*', 73 | ipRestriction( 74 | getConnInfo, 75 | { 76 | denyList: ['192.168.2.0/24'], 77 | }, 78 | async (remote, c) => { 79 | return c.text(`Blocking access from ${remote.addr}`, 403) 80 | } 81 | ) 82 | ) 83 | ``` 84 | -------------------------------------------------------------------------------- /docs/middleware/builtin/jsx-renderer.md: -------------------------------------------------------------------------------- 1 | # JSX Renderer Middleware 2 | 3 | JSX Renderer Middleware allows you to set up the layout when rendering JSX with the `c.render()` function, without the need for using `c.setRenderer()`. Additionally, it enables access to instances of Context within components through the use of `useRequestContext()`. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { jsxRenderer, useRequestContext } from 'hono/jsx-renderer' 10 | ``` 11 | 12 | ## Usage 13 | 14 | ```jsx 15 | const app = new Hono() 16 | 17 | app.get( 18 | '/page/*', 19 | jsxRenderer(({ children }) => { 20 | return ( 21 | 22 | 23 |
Menu
24 |
{children}
25 | 26 | 27 | ) 28 | }) 29 | ) 30 | 31 | app.get('/page/about', (c) => { 32 | return c.render(

About me!

) 33 | }) 34 | ``` 35 | 36 | ## Options 37 | 38 | ### docType: `boolean` | `string` 39 | 40 | If you do not want to add a DOCTYPE at the beginning of the HTML, set the `docType` option to `false`. 41 | 42 | ```tsx 43 | app.use( 44 | '*', 45 | jsxRenderer( 46 | ({ children }) => { 47 | return ( 48 | 49 | {children} 50 | 51 | ) 52 | }, 53 | { docType: false } 54 | ) 55 | ) 56 | ``` 57 | 58 | And you can specify the DOCTYPE. 59 | 60 | ```tsx 61 | app.use( 62 | '*', 63 | jsxRenderer( 64 | ({ children }) => { 65 | return ( 66 | 67 | {children} 68 | 69 | ) 70 | }, 71 | { 72 | docType: 73 | '', 74 | } 75 | ) 76 | ) 77 | ``` 78 | 79 | ### stream: `boolean` | `Record` 80 | 81 | If you set it to `true` or provide a Record value, it will be rendered as a streaming response. 82 | 83 | ```tsx 84 | const AsyncComponent = async () => { 85 | await new Promise((r) => setTimeout(r, 1000)) // sleep 1s 86 | return
Hi!
87 | } 88 | 89 | app.get( 90 | '*', 91 | jsxRenderer( 92 | ({ children }) => { 93 | return ( 94 | 95 | 96 |

SSR Streaming

97 | {children} 98 | 99 | 100 | ) 101 | }, 102 | { stream: true } 103 | ) 104 | ) 105 | 106 | app.get('/', (c) => { 107 | return c.render( 108 | loading...}> 109 | 110 | 111 | ) 112 | }) 113 | ``` 114 | 115 | If `true` is set, the following headers are added: 116 | 117 | ```ts 118 | { 119 | 'Transfer-Encoding': 'chunked', 120 | 'Content-Type': 'text/html; charset=UTF-8', 121 | 'Content-Encoding': 'Identity' 122 | } 123 | ``` 124 | 125 | You can customize the header values by specifying the Record values. 126 | 127 | ## Nested Layouts 128 | 129 | The `Layout` component enables nesting the layouts. 130 | 131 | ```tsx 132 | app.use( 133 | jsxRenderer(({ children }) => { 134 | return ( 135 | 136 | {children} 137 | 138 | ) 139 | }) 140 | ) 141 | 142 | const blog = new Hono() 143 | blog.use( 144 | jsxRenderer(({ children, Layout }) => { 145 | return ( 146 | 147 | 148 |
{children}
149 |
150 | ) 151 | }) 152 | ) 153 | 154 | app.route('/blog', blog) 155 | ``` 156 | 157 | ## `useRequestContext()` 158 | 159 | `useRequestContext()` returns an instance of Context. 160 | 161 | ```tsx 162 | import { useRequestContext, jsxRenderer } from 'hono/jsx-renderer' 163 | 164 | const app = new Hono() 165 | app.use(jsxRenderer()) 166 | 167 | const RequestUrlBadge: FC = () => { 168 | const c = useRequestContext() 169 | return {c.req.url} 170 | } 171 | 172 | app.get('/page/info', (c) => { 173 | return c.render( 174 |
175 | You are accessing: 176 |
177 | ) 178 | }) 179 | ``` 180 | 181 | ::: warning 182 | You can't use `useRequestContext()` with the Deno's `precompile` JSX option. Use the `react-jsx`: 183 | 184 | ```json 185 | "compilerOptions": { 186 | "jsx": "precompile", // [!code --] 187 | "jsx": "react-jsx", // [!code ++] 188 | "jsxImportSource": "hono/jsx" 189 | } 190 | } 191 | ``` 192 | 193 | ::: 194 | 195 | ## Extending `ContextRenderer` 196 | 197 | By defining `ContextRenderer` as shown below, you can pass additional content to the renderer. This is handy, for instance, when you want to change the contents of the head tag depending on the page. 198 | 199 | ```tsx 200 | declare module 'hono' { 201 | interface ContextRenderer { 202 | ( 203 | content: string | Promise, 204 | props: { title: string } 205 | ): Response 206 | } 207 | } 208 | 209 | const app = new Hono() 210 | 211 | app.get( 212 | '/page/*', 213 | jsxRenderer(({ children, title }) => { 214 | return ( 215 | 216 | 217 | {title} 218 | 219 | 220 |
Menu
221 |
{children}
222 | 223 | 224 | ) 225 | }) 226 | ) 227 | 228 | app.get('/page/favorites', (c) => { 229 | return c.render( 230 |
231 |
    232 |
  • Eating sushi
  • 233 |
  • Watching baseball games
  • 234 |
235 |
, 236 | { 237 | title: 'My favorites', 238 | } 239 | ) 240 | }) 241 | ``` 242 | -------------------------------------------------------------------------------- /docs/middleware/builtin/jwk.md: -------------------------------------------------------------------------------- 1 | # JWK Auth Middleware 2 | 3 | The JWK Auth Middleware authenticates requests by verifying tokens using JWK (JSON Web Key). It checks for an `Authorization` header and other configured sources, such as cookies, if specified. Specifically, it validates tokens using the provided `keys`, retrieves keys from `jwks_uri` if specified, and supports token extraction from cookies if the `cookie` option is set. 4 | 5 | :::info 6 | The Authorization header sent from the client must have a specified scheme. 7 | 8 | Example: `Bearer my.token.value` or `Basic my.token.value` 9 | ::: 10 | 11 | ## Import 12 | 13 | ```ts 14 | import { Hono } from 'hono' 15 | import { jwk } from 'hono/jwk' 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```ts 21 | const app = new Hono() 22 | 23 | app.use( 24 | '/auth/*', 25 | jwk({ 26 | jwks_uri: `https://${backendServer}/.well-known/jwks.json`, 27 | }) 28 | ) 29 | 30 | app.get('/auth/page', (c) => { 31 | return c.text('You are authorized') 32 | }) 33 | ``` 34 | 35 | Get payload: 36 | 37 | ```ts 38 | const app = new Hono() 39 | 40 | app.use( 41 | '/auth/*', 42 | jwk({ 43 | jwks_uri: `https://${backendServer}/.well-known/jwks.json`, 44 | }) 45 | ) 46 | 47 | app.get('/auth/page', (c) => { 48 | const payload = c.get('jwtPayload') 49 | return c.json(payload) // eg: { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } 50 | }) 51 | ``` 52 | 53 | ## Options 54 | 55 | ### keys: `HonoJsonWebKey[] | (() => Promise)` 56 | 57 | The values of your public keys, or a function that returns them. 58 | 59 | ### jwks_uri: `string` 60 | 61 | If this value is set, attempt to fetch JWKs from this URI, expecting a JSON response with `keys`, which are added to the provided `keys` option. 62 | 63 | ### cookie: `string` 64 | 65 | If this value is set, then the value is retrieved from the cookie header using that value as a key, which is then validated as a token. 66 | -------------------------------------------------------------------------------- /docs/middleware/builtin/jwt.md: -------------------------------------------------------------------------------- 1 | # JWT Auth Middleware 2 | 3 | The JWT Auth Middleware provides authentication by verifying the token with JWT. 4 | The middleware will check for an `Authorization` header if the `cookie` option is not set. 5 | 6 | :::info 7 | The Authorization header sent from the client must have a specified scheme. 8 | 9 | Example: `Bearer my.token.value` or `Basic my.token.value` 10 | ::: 11 | 12 | ## Import 13 | 14 | ```ts 15 | import { Hono } from 'hono' 16 | import { jwt } from 'hono/jwt' 17 | import type { JwtVariables } from 'hono/jwt' 18 | ``` 19 | 20 | ## Usage 21 | 22 | ```ts 23 | // Specify the variable types to infer the `c.get('jwtPayload')`: 24 | type Variables = JwtVariables 25 | 26 | const app = new Hono<{ Variables: Variables }>() 27 | 28 | app.use( 29 | '/auth/*', 30 | jwt({ 31 | secret: 'it-is-very-secret', 32 | }) 33 | ) 34 | 35 | app.get('/auth/page', (c) => { 36 | return c.text('You are authorized') 37 | }) 38 | ``` 39 | 40 | Get payload: 41 | 42 | ```ts 43 | const app = new Hono() 44 | 45 | app.use( 46 | '/auth/*', 47 | jwt({ 48 | secret: 'it-is-very-secret', 49 | }) 50 | ) 51 | 52 | app.get('/auth/page', (c) => { 53 | const payload = c.get('jwtPayload') 54 | return c.json(payload) // eg: { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } 55 | }) 56 | ``` 57 | 58 | ::: tip 59 | 60 | `jwt()` is just a middleware function. If you want to use an environment variable (eg: `c.env.JWT_SECRET`), you can use it as follows: 61 | 62 | ```js 63 | app.use('/auth/*', (c, next) => { 64 | const jwtMiddleware = jwt({ 65 | secret: c.env.JWT_SECRET, 66 | }) 67 | return jwtMiddleware(c, next) 68 | }) 69 | ``` 70 | 71 | ::: 72 | 73 | ## Options 74 | 75 | ### secret: `string` 76 | 77 | A value of your secret key. 78 | 79 | ### cookie: `string` 80 | 81 | If this value is set, then the value is retrieved from the cookie header using that value as a key, which is then validated as a token. 82 | 83 | ### alg: `string` 84 | 85 | An algorithm type that is used for verifying. 86 | The default is `HS256`. 87 | 88 | Available types are `HS256` | `HS384` | `HS512` | `RS256` | `RS384` | `RS512` | `PS256` | `PS384` | `PS512` | `ES256` | `ES384` | `ES512` | `EdDSA`. 89 | -------------------------------------------------------------------------------- /docs/middleware/builtin/logger.md: -------------------------------------------------------------------------------- 1 | # Logger Middleware 2 | 3 | It's a simple logger. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { logger } from 'hono/logger' 10 | ``` 11 | 12 | ## Usage 13 | 14 | ```ts 15 | const app = new Hono() 16 | 17 | app.use(logger()) 18 | app.get('/', (c) => c.text('Hello Hono!')) 19 | ``` 20 | 21 | ## Logging Details 22 | 23 | The Logger Middleware logs the following details for each request: 24 | 25 | - **Incoming Request**: Logs the HTTP method, request path, and incoming request. 26 | - **Outgoing Response**: Logs the HTTP method, request path, response status code, and request/response times. 27 | - **Status Code Coloring**: Response status codes are color-coded for better visibility and quick identification of status categories. Different status code categories are represented by different colors. 28 | - **Elapsed Time**: The time taken for the request/response cycle is logged in a human-readable format, either in milliseconds (ms) or seconds (s). 29 | 30 | By using the Logger Middleware, you can easily monitor the flow of requests and responses in your Hono application and quickly identify any issues or performance bottlenecks. 31 | 32 | You can also extend the middleware further by providing your own `PrintFunc` function for tailored logging behavior. 33 | 34 | ## PrintFunc 35 | 36 | The Logger Middleware accepts an optional `PrintFunc` function as a parameter. This function allows you to customize the logger and add additional logs. 37 | 38 | ## Options 39 | 40 | ### fn: `PrintFunc(str: string, ...rest: string[])` 41 | 42 | - `str`: Passed by the logger. 43 | - `...rest`: Additional string props to be printed to console. 44 | 45 | ### Example 46 | 47 | Setting up a custom `PrintFunc` function to the Logger Middleware: 48 | 49 | ```ts 50 | export const customLogger = (message: string, ...rest: string[]) => { 51 | console.log(message, ...rest) 52 | } 53 | 54 | app.use(logger(customLogger)) 55 | ``` 56 | 57 | Setting up the custom logger in a route: 58 | 59 | ```ts 60 | app.post('/blog', (c) => { 61 | // Routing logic 62 | 63 | customLogger('Blog saved:', `Path: ${blog.url},`, `ID: ${blog.id}`) 64 | // Output 65 | // <-- POST /blog 66 | // Blog saved: Path: /blog/example, ID: 1 67 | // --> POST /blog 201 93ms 68 | 69 | // Return Context 70 | }) 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/middleware/builtin/method-override.md: -------------------------------------------------------------------------------- 1 | # Method Override Middleware 2 | 3 | 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. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { methodOverride } from 'hono/method-override' 10 | ``` 11 | 12 | ## Usage 13 | 14 | ```ts 15 | const app = new Hono() 16 | 17 | // If no options are specified, the value of `_method` in the form, 18 | // e.g. DELETE, is used as the method. 19 | app.use('/posts', methodOverride({ app })) 20 | 21 | app.delete('/posts', (c) => { 22 | // .... 23 | }) 24 | ``` 25 | 26 | ## For example 27 | 28 | 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. 29 | 30 | The HTML form: 31 | 32 | ```html 33 |
34 | 35 | 36 |
37 | ``` 38 | 39 | The application: 40 | 41 | ```ts 42 | import { methodOverride } from 'hono/method-override' 43 | 44 | const app = new Hono() 45 | app.use('/posts', methodOverride({ app })) 46 | 47 | app.delete('/posts', () => { 48 | // ... 49 | }) 50 | ``` 51 | 52 | You can change the default values or use the header value and query value: 53 | 54 | ```ts 55 | app.use('/posts', methodOverride({ app, form: '_custom_name' })) 56 | app.use( 57 | '/posts', 58 | methodOverride({ app, header: 'X-METHOD-OVERRIDE' }) 59 | ) 60 | app.use('/posts', methodOverride({ app, query: '_method' })) 61 | ``` 62 | 63 | ## Options 64 | 65 | ### app: `Hono` 66 | 67 | The instance of `Hono` is used in your application. 68 | 69 | ### form: `string` 70 | 71 | Form key with a value containing the method name. 72 | The default is `_method`. 73 | 74 | ### header: `boolean` 75 | 76 | Header name with a value containing the method name. 77 | 78 | ### query: `boolean` 79 | 80 | Query parameter key with a value containing the method name. 81 | -------------------------------------------------------------------------------- /docs/middleware/builtin/pretty-json.md: -------------------------------------------------------------------------------- 1 | # Pretty JSON Middleware 2 | 3 | Pretty JSON middleware enables "_JSON pretty print_" for JSON response body. 4 | Adding `?pretty` to url query param, the JSON strings are prettified. 5 | 6 | ```js 7 | // GET / 8 | {"project":{"name":"Hono","repository":"https://github.com/honojs/hono"}} 9 | ``` 10 | 11 | will be: 12 | 13 | ```js 14 | // GET /?pretty 15 | { 16 | "project": { 17 | "name": "Hono", 18 | "repository": "https://github.com/honojs/hono" 19 | } 20 | } 21 | ``` 22 | 23 | ## Import 24 | 25 | ```ts 26 | import { Hono } from 'hono' 27 | import { prettyJSON } from 'hono/pretty-json' 28 | ``` 29 | 30 | ## Usage 31 | 32 | ```ts 33 | const app = new Hono() 34 | 35 | app.use(prettyJSON()) // With options: prettyJSON({ space: 4 }) 36 | app.get('/', (c) => { 37 | return c.json({ message: 'Hono!' }) 38 | }) 39 | ``` 40 | 41 | ## Options 42 | 43 | ### space: `number` 44 | 45 | Number of spaces for indentation. The default is `2`. 46 | 47 | ### query: `string` 48 | 49 | The name of the query string for applying. The default is `pretty`. 50 | -------------------------------------------------------------------------------- /docs/middleware/builtin/request-id.md: -------------------------------------------------------------------------------- 1 | # Request ID Middleware 2 | 3 | Request ID Middleware generates a unique ID for each request, which you can use in your handlers. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { requestId } from 'hono/request-id' 10 | ``` 11 | 12 | ## Usage 13 | 14 | You can access the Request ID through the `requestId` variable in the handlers and middleware to which the Request ID Middleware is applied. 15 | 16 | ```ts 17 | const app = new Hono() 18 | 19 | app.use('*', requestId()) 20 | 21 | app.get('/', (c) => { 22 | return c.text(`Your request id is ${c.get('requestId')}`) 23 | }) 24 | ``` 25 | 26 | If you want to explicitly specify the type, import `RequestIdVariables` and pass it in the generics of `new Hono()`. 27 | 28 | ```ts 29 | import type { RequestIdVariables } from 'hono/request-id' 30 | 31 | const app = new Hono<{ 32 | Variables: RequestIdVariables 33 | }>() 34 | ``` 35 | 36 | ## Options 37 | 38 | ### limitLength: `number` 39 | 40 | The maximum length of the request ID. The default is `255`. 41 | 42 | ### headerName: `string` 43 | 44 | The header name used for the request ID. The default is `X-Request-Id`. 45 | 46 | ### generator: `(c: Context) => string` 47 | 48 | The request ID generation function. By default, it uses `crypto.randomUUID()`. 49 | -------------------------------------------------------------------------------- /docs/middleware/builtin/timeout.md: -------------------------------------------------------------------------------- 1 | # Timeout Middleware 2 | 3 | The Timeout Middleware enables you to easily manage request timeouts in your application. It allows you to set a maximum duration for requests and optionally define custom error responses if the specified timeout is exceeded. 4 | 5 | ## Import 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { timeout } from 'hono/timeout' 10 | ``` 11 | 12 | ## Usage 13 | 14 | Here's how to use the Timeout Middleware with both default and custom settings: 15 | 16 | Default Settings: 17 | 18 | ```ts 19 | const app = new Hono() 20 | 21 | // Applying a 5-second timeout 22 | app.use('/api', timeout(5000)) 23 | 24 | // Handling a route 25 | app.get('/api/data', async (c) => { 26 | // Your route handler logic 27 | return c.json({ data: 'Your data here' }) 28 | }) 29 | ``` 30 | 31 | Custom settings: 32 | 33 | ```ts 34 | import { HTTPException } from 'hono/http-exception' 35 | 36 | // Custom exception factory function 37 | const customTimeoutException = (context) => 38 | new HTTPException(408, { 39 | message: `Request timeout after waiting ${context.req.headers.get( 40 | 'Duration' 41 | )} seconds. Please try again later.`, 42 | }) 43 | 44 | // for Static Exception Message 45 | // const customTimeoutException = new HTTPException(408, { 46 | // message: 'Operation timed out. Please try again later.' 47 | // }); 48 | 49 | // Applying a 1-minute timeout with a custom exception 50 | app.use('/api/long-process', timeout(60000, customTimeoutException)) 51 | 52 | app.get('/api/long-process', async (c) => { 53 | // Simulate a long process 54 | await new Promise((resolve) => setTimeout(resolve, 61000)) 55 | return c.json({ data: 'This usually takes longer' }) 56 | }) 57 | ``` 58 | 59 | ## Notes 60 | 61 | - The duration for the timeout can be specified in milliseconds. The middleware will automatically reject the promise and potentially throw an error if the specified duration is exceeded. 62 | 63 | - The timeout middleware cannot be used with stream Thus, use `stream.close` and `setTimeout` together. 64 | 65 | ```ts 66 | app.get('/sse', async (c) => { 67 | let id = 0 68 | let running = true 69 | let timer: number | undefined 70 | 71 | return streamSSE(c, async (stream) => { 72 | timer = setTimeout(() => { 73 | console.log('Stream timeout reached, closing stream') 74 | stream.close() 75 | }, 3000) as unknown as number 76 | 77 | stream.onAbort(async () => { 78 | console.log('Client closed connection') 79 | running = false 80 | clearTimeout(timer) 81 | }) 82 | 83 | while (running) { 84 | const message = `It is ${new Date().toISOString()}` 85 | await stream.writeSSE({ 86 | data: message, 87 | event: 'time-update', 88 | id: String(id++), 89 | }) 90 | await stream.sleep(1000) 91 | } 92 | }) 93 | }) 94 | ``` 95 | 96 | ## Middleware Conflicts 97 | 98 | Be cautious about the order of middleware, especially when using error-handling or other timing-related middleware, as it might affect the behavior of this timeout middleware. 99 | -------------------------------------------------------------------------------- /docs/middleware/builtin/timing.md: -------------------------------------------------------------------------------- 1 | # Server-Timing Middleware 2 | 3 | The [Server-Timing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing) Middleware provides 4 | performance metrics in the response headers. 5 | 6 | ::: info 7 | Note: On Cloudflare Workers, the timer metrics may not be accurate, 8 | since [timers only show the time of last I/O](https://developers.cloudflare.com/workers/learning/security-model/#step-1-disallow-timers-and-multi-threading). 9 | ::: 10 | 11 | ## Import 12 | 13 | ```ts [npm] 14 | import { Hono } from 'hono' 15 | import { timing, setMetric, startTime, endTime } from 'hono/timing' 16 | import type { TimingVariables } from 'hono/timing' 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```js 22 | // Specify the variable types to infer the `c.get('metric')`: 23 | type Variables = TimingVariables 24 | 25 | const app = new Hono<{ Variables: Variables }>() 26 | 27 | // add the middleware to your router 28 | app.use(timing()); 29 | 30 | app.get('/', async (c) => { 31 | 32 | // add custom metrics 33 | setMetric(c, 'region', 'europe-west3') 34 | 35 | // add custom metrics with timing, must be in milliseconds 36 | setMetric(c, 'custom', 23.8, 'My custom Metric') 37 | 38 | // start a new timer 39 | startTime(c, 'db'); 40 | const data = await db.findMany(...); 41 | 42 | // end the timer 43 | endTime(c, 'db'); 44 | 45 | return c.json({ response: data }); 46 | }); 47 | ``` 48 | 49 | ### Conditionally enabled 50 | 51 | ```ts 52 | const app = new Hono() 53 | 54 | app.use( 55 | '*', 56 | timing({ 57 | // c: Context of the request 58 | enabled: (c) => c.req.method === 'POST', 59 | }) 60 | ) 61 | ``` 62 | 63 | ## Result 64 | 65 | ![](/images/timing-example.png) 66 | 67 | ## Options 68 | 69 | ### total: `boolean` 70 | 71 | Show the total response time. The default is `true`. 72 | 73 | ### enabled: `boolean` | `(c: Context) => boolean` 74 | 75 | Whether timings should be added to the headers or not. The default is `true`. 76 | 77 | ### totalDescription: `boolean` 78 | 79 | Description for the total response time. The default is `Total Response Time`. 80 | 81 | ### autoEnd: `boolean` 82 | 83 | If `startTime()` should end automatically at the end of the request. 84 | If disabled, not manually ended timers will not be shown. 85 | 86 | ### crossOrigin: `boolean` | `string` | `(c: Context) => boolean | string` 87 | 88 | The origin this timings header should be readable. 89 | 90 | - If false, only from current origin. 91 | - If true, from all origin. 92 | - If string, from this domain(s). Multiple domains must be separated with a comma. 93 | 94 | The default is `false`. See more [docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin). 95 | -------------------------------------------------------------------------------- /docs/middleware/builtin/trailing-slash.md: -------------------------------------------------------------------------------- 1 | # Trailing Slash Middleware 2 | 3 | This middleware handles Trailing Slash in the URL on a GET request. 4 | 5 | `appendTrailingSlash` redirects the URL to which it added the Trailing Slash if the content was not found. Also, `trimTrailingSlash` will remove the Trailing Slash. 6 | 7 | ## Import 8 | 9 | ```ts 10 | import { Hono } from 'hono' 11 | import { 12 | appendTrailingSlash, 13 | trimTrailingSlash, 14 | } from 'hono/trailing-slash' 15 | ``` 16 | 17 | ## Usage 18 | 19 | Example of redirecting a GET request of `/about/me` to `/about/me/`. 20 | 21 | ```ts 22 | import { Hono } from 'hono' 23 | import { appendTrailingSlash } from 'hono/trailing-slash' 24 | 25 | const app = new Hono({ strict: true }) 26 | 27 | app.use(appendTrailingSlash()) 28 | app.get('/about/me/', (c) => c.text('With Trailing Slash')) 29 | ``` 30 | 31 | Example of redirecting a GET request of `/about/me/` to `/about/me`. 32 | 33 | ```ts 34 | import { Hono } from 'hono' 35 | import { trimTrailingSlash } from 'hono/trailing-slash' 36 | 37 | const app = new Hono({ strict: true }) 38 | 39 | app.use(trimTrailingSlash()) 40 | app.get('/about/me', (c) => c.text('Without Trailing Slash')) 41 | ``` 42 | 43 | ## Note 44 | 45 | It will be enabled when the request method is `GET` and the response status is `404`. 46 | -------------------------------------------------------------------------------- /docs/middleware/third-party.md: -------------------------------------------------------------------------------- 1 | # Third-party Middleware 2 | 3 | Third-party middleware refers to middleware not bundled within the Hono package. 4 | Most of this middleware leverages external libraries. 5 | 6 | ### Authentication 7 | 8 | - [Auth.js(Next Auth)](https://github.com/honojs/middleware/tree/main/packages/auth-js) 9 | - [Clerk Auth](https://github.com/honojs/middleware/tree/main/packages/clerk-auth) 10 | - [OAuth Providers](https://github.com/honojs/middleware/tree/main/packages/oauth-providers) 11 | - [OIDC Auth](https://github.com/honojs/middleware/tree/main/packages/oidc-auth) 12 | - [Firebase Auth](https://github.com/honojs/middleware/tree/main/packages/firebase-auth) 13 | - [Verify RSA JWT (JWKS)](https://github.com/wataruoguchi/verify-rsa-jwt-cloudflare-worker) 14 | 15 | ### Validators 16 | 17 | - [ArkType validator](https://github.com/honojs/middleware/tree/main/packages/arktype-validator) 18 | - [Effect Schema Validator](https://github.com/honojs/middleware/tree/main/packages/effect-validator) 19 | - [Standard Schema Validator](https://github.com/honojs/middleware/tree/main/packages/standard-validator) 20 | - [TypeBox Validator](https://github.com/honojs/middleware/tree/main/packages/typebox-validator) 21 | - [Typia Validator](https://github.com/honojs/middleware/tree/main/packages/typia-validator) 22 | - [unknownutil Validator](https://github.com/ryoppippi/hono-unknownutil-validator) 23 | - [Valibot Validator](https://github.com/honojs/middleware/tree/main/packages/valibot-validator) 24 | - [Zod Validator](https://github.com/honojs/middleware/tree/main/packages/zod-validator) 25 | 26 | ### OpenAPI 27 | 28 | - [Zod OpenAPI](https://github.com/honojs/middleware/tree/main/packages/zod-openapi) 29 | - [Scalar API Reference](https://github.com/scalar/scalar/tree/main/integrations/hono) 30 | - [Swagger UI](https://github.com/honojs/middleware/tree/main/packages/swagger-ui) 31 | - [Hono OpenAPI](https://github.com/rhinobase/hono-openapi) 32 | 33 | ### Others 34 | 35 | - [Bun Transpiler](https://github.com/honojs/middleware/tree/main/packages/bun-transpiler) 36 | - [esbuild Transpiler](https://github.com/honojs/middleware/tree/main/packages/esbuild-transpiler) 37 | - [Event Emitter](https://github.com/honojs/middleware/tree/main/packages/event-emitter) 38 | - [GraphQL Server](https://github.com/honojs/middleware/tree/main/packages/graphql-server) 39 | - [Hono Rate Limiter](https://github.com/rhinobase/hono-rate-limiter) 40 | - [Node WebSocket Helper](https://github.com/honojs/middleware/tree/main/packages/node-ws) 41 | - [Prometheus Metrics](https://github.com/honojs/middleware/tree/main/packages/prometheus) 42 | - [Qwik City](https://github.com/honojs/middleware/tree/main/packages/qwik-city) 43 | - [React Compatibility](https://github.com/honojs/middleware/tree/main/packages/react-compat) 44 | - [React Renderer](https://github.com/honojs/middleware/tree/main/packages/react-renderer) 45 | - [RONIN (Database)](https://github.com/ronin-co/hono-client) 46 | - [Sentry](https://github.com/honojs/middleware/tree/main/packages/sentry) 47 | - [tRPC Server](https://github.com/honojs/middleware/tree/main/packages/trpc-server) 48 | - [Geo](https://github.com/ktkongtong/hono-geo-middleware/tree/main/packages/middleware) 49 | - [Hono Simple DI](https://github.com/maou-shonen/hono-simple-DI) 50 | - [Highlight.io](https://www.highlight.io/docs/getting-started/backend-sdk/js/hono) 51 | - [Apitally (API monitoring & analytics)](https://docs.apitally.io/frameworks/hono) 52 | - [Cap Checkpoint](https://capjs.js.org/guide/middleware/hono.html) 53 | -------------------------------------------------------------------------------- /examples/better-auth.md: -------------------------------------------------------------------------------- 1 | # Better Auth 2 | 3 | Using Hono with [Better Auth](http://better-auth.com/) for authentication. 4 | 5 | Better Auth is a framework-agnostic authentication and authorization framework for TypeScript. It provides a comprehensive set of features out of the box and includes a plugin ecosystem that simplifies adding advanced functionalities. 6 | 7 | ## Configuration 8 | 9 | 1. Install the framework: 10 | 11 | ```sh 12 | # npm 13 | npm install better-auth 14 | 15 | # bun 16 | bun add better-auth 17 | 18 | # pnpm 19 | pnpm add better-auth 20 | 21 | # yarn 22 | yarn add better-auth 23 | ``` 24 | 25 | 2. Add the required environment variables in the `.env` file: 26 | 27 | ```sh 28 | BETTER_AUTH_SECRET= (e.g. D27gijdvth3Ul3DjGcexjcFfgCHc8jWd) 29 | BETTER_AUTH_URL= (e.g. http://localhost:1234) 30 | ``` 31 | 32 | 3. Create the Better Auth instance 33 | 34 | ```ts 35 | import { betterAuth } from 'better-auth' 36 | import { prismaAdapter } from 'better-auth/adapters/prisma' 37 | 38 | import prisma from '@/db/index' 39 | import env from '@/env' 40 | 41 | export const auth = betterAuth({ 42 | database: prismaAdapter(prisma, { 43 | provider: 'postgresql', 44 | }), 45 | // Allow requests from the frontend development server 46 | trustedOrigins: ['http://localhost:5173'], 47 | emailAndPassword: { 48 | enabled: true, 49 | }, 50 | socialProviders: { 51 | github: { 52 | clientId: env.GITHUB_CLIENT_ID, 53 | clientSecret: env.GITHUB_CLIENT_SECRET, 54 | }, 55 | google: { 56 | clientId: env.GOOGLE_CLIENT_ID, 57 | clientSecret: env.GOOGLE_CLIENT_SECRET, 58 | }, 59 | }, 60 | }) 61 | 62 | export type AuthType = { 63 | user: typeof auth.$Infer.Session.user | null 64 | session: typeof auth.$Infer.Session.session | null 65 | } 66 | ``` 67 | 68 | The above code: 69 | 70 | - Sets up the database to use Prisma ORM and PostgreSQL 71 | - Specifies the trusted origins 72 | - A trusted origin is the app that's allowed to make requests to the auth API. Normally, that's your client (frontend) 73 | - All the other origins are automatically blocked 74 | - It enables email/password authentication and configures social login providers. 75 | 76 | 4. Generate all the required models, fields, and relationships to the Prisma schema file: 77 | 78 | ```sh 79 | bunx @better-auth/cli generate 80 | ``` 81 | 82 | 5. Create the API Handler for the auth API requests in `routes/auth.ts` 83 | 84 | This route uses the handler provided by Better Auth to serve all `POST` and `GET` requests to the `/api/auth` endpoint. 85 | 86 | ```ts 87 | import { Hono } from 'hono' 88 | import { auth } from '../lib/auth' 89 | import type { AuthType } from '../lib/auth' 90 | 91 | const router = new Hono<{ Bindings: AuthType }>({ 92 | strict: false, 93 | }) 94 | 95 | router.on(['POST', 'GET'], '/auth/*', (c) => { 96 | return auth.handler(c.req.raw) 97 | }) 98 | 99 | export default router 100 | ``` 101 | 102 | 6. Mount the route 103 | 104 | The code below mounts the route. 105 | 106 | ```ts 107 | import { Hono } from "hono"; 108 | import type { AuthType } from "../lib/auth" 109 | import auth from "@/routes/auth"; 110 | 111 | const app = new Hono<{ Variables: AuthType }>({ 112 | strict: false, 113 | }); 114 | 115 | const routes = [auth, ...other routes] as const; 116 | 117 | routes.forEach((route) => { 118 | app.basePath("/api").route("/", route); 119 | }); 120 | 121 | export default app; 122 | ``` 123 | 124 | ## See also 125 | 126 | - [Repository with the complete code](https://github.com/catalinpit/example-app/) 127 | - [Better Auth with Hono, Bun, TypeScript, React and Vite](https://catalins.tech/better-auth-with-hono-bun-typescript-react-vite/) 128 | -------------------------------------------------------------------------------- /examples/cbor.md: -------------------------------------------------------------------------------- 1 | # CBOR 2 | 3 | [CBOR](https://cbor.io/) is a binary format for serializing objects defined in [RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html). It is JSON-compatible and suitable for network communications that require efficient data exchange, as well as for use in resource-constrained environments such as IoT devices. 4 | 5 | Here's an example of using [cbor2](https://www.npmjs.com/package/cbor2) package to respond with CBOR: 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { createMiddleware } from 'hono/factory' 10 | import { encode } from 'cbor2' 11 | 12 | const app = new Hono() 13 | 14 | declare module 'hono' { 15 | interface ContextRenderer { 16 | (content: any): Response | Promise 17 | } 18 | } 19 | 20 | const cborRenderer = createMiddleware(async (c, next) => { 21 | c.header('Content-Type', 'application/cbor') 22 | c.setRenderer((content) => { 23 | return c.body(encode(content)) 24 | }) 25 | await next() 26 | }) 27 | 28 | app.use(cborRenderer) 29 | 30 | app.get('/', (c) => { 31 | return c.render({ message: 'hello CBOR!' }) 32 | }) 33 | 34 | export default app 35 | ``` 36 | 37 | You can check the response using the following command. 38 | 39 | ```plaintext 40 | $ curl -s http://localhost:3000/ | hexdump -C 41 | 00000000 a1 67 6d 65 73 73 61 67 65 6b 68 65 6c 6c 6f 20 |.gmessagekhello | 42 | 00000010 43 42 4f 52 21 |CBOR!| 43 | 00000015 44 | ``` 45 | 46 | Additionally, you can verify that it decodes to a JSON object in the [CBOR playground](https://cbor.me/). 47 | 48 | ```plaintext 49 | A1 # map(1) 50 | 67 # text(7) 51 | 6D657373616765 # "message" 52 | 6B # text(11) 53 | 68656C6C6F2043424F5221 # "hello CBOR!" 54 | ``` 55 | 56 | ```json 57 | { "message": "hello CBOR!" } 58 | ``` 59 | 60 | ## See also 61 | 62 | - [CBOR — Concise Binary Object Representation | Overview](https://cbor.io/) 63 | - [CBOR playground](https://cbor.me/) 64 | -------------------------------------------------------------------------------- /examples/cloudflare-durable-objects.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Durable Objects 2 | 3 | Cloudflare Durable Objects cannot handle HTTP requests directly. Instead, they work through a two-step process: 4 | 5 | 1. A Worker receives HTTP fetch requests from clients 6 | 2. The Worker makes RPC (Remote Procedure Call) invocations to the Durable Object 7 | 3. The Durable Object processes the RPC and returns the result to the Worker 8 | 4. The Worker sends the HTTP response back to the client 9 | 10 | You can use Hono as the router in your Cloudflare Worker, calling RPCs (Remote Procedure Calls) to interact with [Durable Objects](https://developers.cloudflare.com/durable-objects/). This is the recommended approach as of Cloudflare Workers compatibility date `2024-04-03`. 11 | 12 | ## Example: Counter Durable Object 13 | 14 | ```ts 15 | import { DurableObject } from 'cloudflare:workers' 16 | import { Hono } from 'hono' 17 | 18 | export class Counter extends DurableObject { 19 | // In-memory state 20 | value = 0 21 | 22 | constructor(ctx: DurableObjectState, env: Env) { 23 | super(ctx, env) 24 | 25 | // `blockConcurrencyWhile()` ensures no requests are delivered until initialization completes. 26 | ctx.blockConcurrencyWhile(async () => { 27 | // After initialization, future reads do not need to access storage. 28 | this.value = (await ctx.storage.get('value')) || 0 29 | }) 30 | } 31 | 32 | async getCounterValue() { 33 | return this.value 34 | } 35 | 36 | async increment(amount = 1): Promise { 37 | this.value += amount 38 | await this.ctx.storage.put('value', this.value) 39 | return this.value 40 | } 41 | 42 | async decrement(amount = 1): Promise { 43 | this.value -= amount 44 | await this.ctx.storage.put('value', this.value) 45 | return this.value 46 | } 47 | } 48 | 49 | // Create a new Hono app to handle incoming HTTP requests 50 | type Bindings = { 51 | COUNTER: DurableObjectNamespace 52 | } 53 | 54 | const app = new Hono<{ Bindings: Bindings }>() 55 | 56 | // Add routes to interact with the Durable Object 57 | app.get('/counter', async (c) => { 58 | const env = c.env 59 | const id = env.COUNTER.idFromName('counter') 60 | const stub = env.COUNTER.get(id) 61 | const counterValue = await stub.getCounterValue() 62 | return c.text(counterValue.toString()) 63 | }) 64 | 65 | app.post('/counter/increment', async (c) => { 66 | const env = c.env 67 | const id = env.COUNTER.idFromName('counter') 68 | const stub = env.COUNTER.get(id) 69 | const value = await stub.increment() 70 | return c.text(value.toString()) 71 | }) 72 | 73 | app.post('/counter/decrement', async (c) => { 74 | const env = c.env 75 | const id = env.COUNTER.idFromName('counter') 76 | const stub = env.COUNTER.get(id) 77 | const value = await stub.decrement() 78 | return c.text(value.toString()) 79 | }) 80 | 81 | // Export the Hono app as the Worker's fetch handler 82 | export default app 83 | ``` 84 | 85 | `wrangler.jsonc`: 86 | 87 | ```jsonc 88 | { 89 | "$schema": "node_modules/wrangler/config-schema.json", 90 | "name": "durable", 91 | "main": "src/index.ts", 92 | "compatibility_date": "2025-04-14", 93 | "migrations": [ 94 | { 95 | "new_sqlite_classes": ["Counter"], 96 | "tag": "v1", 97 | }, 98 | ], 99 | "durable_objects": { 100 | "bindings": [ 101 | { 102 | "class_name": "Counter", 103 | "name": "COUNTER", 104 | }, 105 | ], 106 | }, 107 | "observability": { 108 | "enabled": true, 109 | }, 110 | } 111 | ``` 112 | 113 | Now you have a fully functional Hono application that interfaces with your Durable Object! The Hono router provides a clean API interface to interact with and expose your Durable Object's methods. 114 | -------------------------------------------------------------------------------- /examples/cloudflare-queue.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Queues 2 | 3 | Using Hono with [Cloudflare Queues](https://developers.cloudflare.com/queues/). 4 | 5 | :::code-group 6 | 7 | ```ts [index.ts] 8 | import { Hono } from 'hono' 9 | 10 | type Environment = { 11 | readonly ERROR_QUEUE: Queue 12 | readonly ERROR_BUCKET: R2Bucket 13 | } 14 | 15 | const app = new Hono<{ 16 | Bindings: Environment 17 | }>() 18 | 19 | app.get('/', (c) => { 20 | if (Math.random() < 0.5) { 21 | return c.text('Success!') 22 | } 23 | throw new Error('Failed!') 24 | }) 25 | 26 | app.onError(async (err, c) => { 27 | await c.env.ERROR_QUEUE.send(err) 28 | return c.text(err.message, { status: 500 }) 29 | }) 30 | 31 | export default { 32 | fetch: app.fetch, 33 | async queue(batch: MessageBatch, env: Environment) { 34 | let file = '' 35 | for (const message of batch.messages) { 36 | const error = message.body 37 | file += error.stack || error.message || String(error) 38 | file += '\r\n' 39 | } 40 | await env.ERROR_BUCKET.put(`errors/${Date.now()}.log`, file) 41 | }, 42 | } 43 | ``` 44 | 45 | ```toml [wrangler.toml] 46 | name = "my-worker" 47 | 48 | [[queues.producers]] 49 | queue = "my-queue" 50 | binding = "ERROR_QUEUE" 51 | 52 | [[queues.consumers]] 53 | queue = "my-queue" 54 | max_batch_size = 100 55 | max_batch_timeout = 30 56 | 57 | [[r2_buckets]] 58 | bucket_name = "my-bucket" 59 | binding = "ERROR_BUCKET" 60 | ``` 61 | 62 | ::: 63 | 64 | ## See also 65 | 66 | - [Cloudflare Queues](https://developers.cloudflare.com/queues/) 67 | - [Cloudflare Queues with R2 Example](https://developers.cloudflare.com/queues/examples/send-errors-to-r2/) 68 | -------------------------------------------------------------------------------- /examples/cloudflare-vitest.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Testing 2 | 3 | You can implement the Cloudflare testing easily with `@cloudflare/vitest-pool-workers` for which some configuration has to be made priorly more on that can be found over in [Cloudflare Docs about testing](https://developers.cloudflare.com/workers/testing/vitest-integration/get-started/write-your-first-test/). 4 | 5 | Cloudflare Testing with vitest pool workers provide a `cloudflare:test` module at runtime which exposes the env passed in as the second argument during testing more on it in the [Cloudflare Test APIs section](https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/). 6 | 7 | Below is an example of the configuration one can make: 8 | 9 | :::code-group 10 | 11 | ```ts [vitest.config.ts] 12 | import { defineWorkersProject } from '@cloudflare/vitest-pool-workers/config' 13 | 14 | export default defineWorkersProject(() => { 15 | return { 16 | test: { 17 | globals: true, 18 | poolOptions: { 19 | workers: { wrangler: { configPath: './wrangler.toml' } }, 20 | }, 21 | }, 22 | } 23 | }) 24 | ``` 25 | 26 | ```toml [wrangler.toml] 27 | compatibility_date = "2024-09-09" 28 | compatibility_flags = [ "nodejs_compat" ] 29 | 30 | [vars] 31 | MY_VAR = "my variable" 32 | ``` 33 | 34 | ::: 35 | 36 | Imagine the application like the following: 37 | 38 | ```ts 39 | // src/index.ts 40 | import { Hono } from 'hono' 41 | 42 | type Bindings = { 43 | MY_VAR: string 44 | } 45 | 46 | const app = new Hono<{ Bindings: Bindings }>() 47 | 48 | app.get('/hello', (c) => { 49 | return c.json({ hello: 'world', var: c.env.MY_VAR }) 50 | }) 51 | 52 | export default app 53 | ``` 54 | 55 | You can test the application with Cloudflare Bindings by passing in the `env` exposed from the module `cloudflare:test` to `app.request()`: 56 | 57 | ```ts 58 | // src/index.test.ts 59 | import { env } from 'cloudflare:test' 60 | import app from './index' 61 | 62 | describe('Example', () => { 63 | it('Should return 200 response', async () => { 64 | const res = await app.request('/hello', {}, env) 65 | 66 | expect(res.status).toBe(200) 67 | expect(await res.json()).toEqual({ 68 | hello: 'world', 69 | var: 'my variable', 70 | }) 71 | }) 72 | }) 73 | ``` 74 | 75 | ## See Also 76 | 77 | `@cloudflare/vitest-pool-workers` [Github Repository examples](https://github.com/cloudflare/workers-sdk/tree/main/fixtures/vitest-pool-workers-examples)\ 78 | [Migrate from old testing system](https://developers.cloudflare.com/workers/testing/vitest-integration/get-started/migrate-from-miniflare-2/) 79 | -------------------------------------------------------------------------------- /examples/file-upload.md: -------------------------------------------------------------------------------- 1 | # File Upload 2 | 3 | You can upload a file with `multipart/form-data` content type. The uploaded file will be available in `c.req.parseBody()`. 4 | 5 | ```ts 6 | const app = new Hono() 7 | 8 | app.post('/upload', async (c) => { 9 | const body = await c.req.parseBody() 10 | console.log(body['file']) // File | string 11 | }) 12 | ``` 13 | 14 | ## See also 15 | 16 | - [API - HonoRequest - parseBody](/docs/api/request#parsebody) 17 | -------------------------------------------------------------------------------- /examples/grouping-routes-rpc.md: -------------------------------------------------------------------------------- 1 | # Grouping routes for RPC 2 | 3 | If you want to enable type inference for multiple `app`s correctly, you can use `app.route()` as follows. 4 | 5 | Pass the value returned from methods like `app.get()` or `app.post()` to the second argument of `app.route()`. 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | import { hc } from 'hono/client' 10 | 11 | const authorsApp = new Hono() 12 | .get('/', (c) => c.json({ result: 'list authors' })) 13 | .post('/', (c) => c.json({ result: 'create an author' }, 201)) 14 | .get('/:id', (c) => c.json({ result: `get ${c.req.param('id')}` })) 15 | 16 | const booksApp = new Hono() 17 | .get('/', (c) => c.json({ result: 'list books' })) 18 | .post('/', (c) => c.json({ result: 'create a book' }, 201)) 19 | .get('/:id', (c) => c.json({ result: `get ${c.req.param('id')}` })) 20 | 21 | const app = new Hono() 22 | .route('/authors', authorsApp) 23 | .route('/books', booksApp) 24 | 25 | type AppType = typeof app 26 | ``` 27 | 28 | ## See also 29 | 30 | - [Guides - RPC - Client](/docs/guides/rpc#client) 31 | -------------------------------------------------------------------------------- /examples/hono-openapi.md: -------------------------------------------------------------------------------- 1 | # Hono OpenAPI 2 | 3 | [hono-openapi](https://github.com/rhinobase/hono-openapi) is a _middleware_ which enables automatic OpenAPI documentation generation for your Hono API by integrating with validation libraries like Zod, Valibot, ArkType, and TypeBox. 4 | 5 | ## 🛠️ Installation 6 | 7 | Install the package along with your preferred validation library and its dependencies: 8 | 9 | ```bash 10 | # For Zod 11 | pnpm add hono-openapi @hono/zod-validator zod zod-openapi 12 | 13 | # For Valibot 14 | pnpm add hono-openapi @hono/valibot-validator valibot @valibot/to-json-schema 15 | 16 | # For ArkType 17 | pnpm add hono-openapi @hono/arktype-validator arktype 18 | 19 | # For TypeBox 20 | pnpm add hono-openapi @hono/typebox-validator @sinclair/typebox 21 | ``` 22 | 23 | --- 24 | 25 | ## 🚀 Getting Started 26 | 27 | ### 1. Define Your Schemas 28 | 29 | Define your request and response schemas using your preferred validation library. Here's an example using Valibot: 30 | 31 | ```ts 32 | import * as v from 'valibot' 33 | 34 | const querySchema = v.object({ 35 | name: v.optional(v.string()), 36 | }) 37 | 38 | const responseSchema = v.string() 39 | ``` 40 | 41 | --- 42 | 43 | ### 2. Create Routes 44 | 45 | Use `describeRoute` for route documentation and validation: 46 | 47 | ```ts 48 | import { Hono } from 'hono' 49 | import { describeRoute } from 'hono-openapi' 50 | // You can import these for your preferred validation library 51 | import { 52 | resolver, 53 | validator as vValidator, 54 | } from 'hono-openapi/valibot' 55 | 56 | const app = new Hono() 57 | 58 | app.get( 59 | '/', 60 | describeRoute({ 61 | description: 'Say hello to the user', 62 | responses: { 63 | 200: { 64 | description: 'Successful response', 65 | content: { 66 | 'text/plain': { schema: resolver(responseSchema) }, 67 | }, 68 | }, 69 | }, 70 | }), 71 | vValidator('query', querySchema), 72 | (c) => { 73 | const query = c.req.valid('query') 74 | return c.text(`Hello ${query?.name ?? 'Hono'}!`) 75 | } 76 | ) 77 | ``` 78 | 79 | --- 80 | 81 | ### 3. Generate OpenAPI Spec 82 | 83 | Add an endpoint for your OpenAPI document: 84 | 85 | ```ts 86 | import { openAPISpecs } from 'hono-openapi' 87 | 88 | app.get( 89 | '/openapi', 90 | openAPISpecs(app, { 91 | documentation: { 92 | info: { 93 | title: 'Hono API', 94 | version: '1.0.0', 95 | description: 'Greeting API', 96 | }, 97 | servers: [ 98 | { url: 'http://localhost:3000', description: 'Local Server' }, 99 | ], 100 | }, 101 | }) 102 | ) 103 | ``` 104 | 105 | --- 106 | 107 | ## 🔍 Advanced Features 108 | 109 | ### Add Security Definitions 110 | 111 | ```ts 112 | app.get( 113 | '/openapi', 114 | openAPISpecs(app, { 115 | documentation: { 116 | components: { 117 | securitySchemes: { 118 | bearerAuth: { 119 | type: 'http', 120 | scheme: 'bearer', 121 | bearerFormat: 'JWT', 122 | }, 123 | }, 124 | }, 125 | security: [{ bearerAuth: [] }], 126 | }, 127 | }) 128 | ) 129 | ``` 130 | 131 | ### Conditionally Hide Routes 132 | 133 | ```ts 134 | app.get( 135 | '/', 136 | describeRoute({ 137 | // ... 138 | hide: process.env.NODE_ENV === 'production', 139 | }), 140 | (c) => c.text('Hidden Route') 141 | ) 142 | ``` 143 | 144 | ### Validate Responses 145 | 146 | ```ts 147 | app.get( 148 | '/', 149 | describeRoute({ 150 | // ... 151 | validateResponse: true, 152 | }), 153 | (c) => c.text('Validated Response') 154 | ) 155 | ``` 156 | 157 | --- 158 | 159 | You can find more examples and detailed documentation in the [hono-openapi repository](https://github.com/rhinobase/hono-openapi). 160 | -------------------------------------------------------------------------------- /examples/htmx.md: -------------------------------------------------------------------------------- 1 | # htmx 2 | 3 | Using Hono with [htmx](https://htmx.org/). 4 | 5 | ## typed-htmx 6 | 7 | By using [typed-htmx](https://github.com/Desdaemon/typed-htmx), you can write JSX with TypeScript definitions for htmx attributes. 8 | We can follow the same pattern found on the [typed-htmx Example Project](https://github.com/Desdaemon/typed-htmx/blob/main/example/src/types.d.ts) to use it with `hono/jsx`. 9 | 10 | Install the package: 11 | 12 | ```sh 13 | npm i -D typed-htmx 14 | ``` 15 | 16 | On `src/global.d.ts` (or `app/global.d.ts` if you're using HonoX), import the `typed-htmx` types: 17 | 18 | ```ts 19 | import 'typed-htmx' 20 | ``` 21 | 22 | Extend Hono's JSX types with the typed-htmx definitions: 23 | 24 | ```ts 25 | // A demo of how to augment foreign types with htmx attributes. 26 | // In this case, Hono sources its types from its own namespace, so we do the same 27 | // and directly extend its namespace. 28 | declare module 'hono/jsx' { 29 | namespace JSX { 30 | interface HTMLAttributes extends HtmxAttributes {} 31 | } 32 | } 33 | ``` 34 | 35 | ## See also 36 | 37 | - [htmx](https://htmx.org/) 38 | - [typed-htmx](https://github.com/Desdaemon/typed-htmx) 39 | -------------------------------------------------------------------------------- /examples/index.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Examples 6 | 7 | In this section, you can see practical examples to create your application with Hono. 8 | 9 |
10 |
11 |

{{ category.text }}

12 | 15 |
16 |
17 | 18 | ## GitHub repository 19 | 20 | You can also see the examples in the GitHub repository: [Hono Examples](https://github.com/honojs/examples) 21 | -------------------------------------------------------------------------------- /examples/menu.data.ts: -------------------------------------------------------------------------------- 1 | import { sidebarsExamples } from '../.vitepress/config' 2 | 3 | export default { 4 | load() { 5 | return { 6 | sidebarsExamples: sidebarsExamples(), 7 | } 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /examples/proxy.md: -------------------------------------------------------------------------------- 1 | # Proxy 2 | 3 | ::: tip 4 | **Update:** We've introduced the new Proxy Helper for easier proxy functionality. Check out the [Proxy Helper documentation](https://hono.dev/docs/helpers/proxy) for more details. 5 | ::: 6 | 7 | ```ts 8 | import { Hono } from 'hono' 9 | 10 | const app = new Hono() 11 | 12 | app.get('/posts/:filename{.+.png$}', (c) => { 13 | const referer = c.req.header('Referer') 14 | if (referer && !/^https:\/\/example.com/.test(referer)) { 15 | return c.text('Forbidden', 403) 16 | } 17 | return fetch(c.req.url) 18 | }) 19 | 20 | app.get('*', (c) => { 21 | return fetch(c.req.url) 22 | }) 23 | 24 | export default app 25 | ``` 26 | 27 | ::: tip 28 | If you can see `Can't modify immutable headers.` error with a similar code, you need to clone the response object. 29 | 30 | ```ts 31 | app.get('/', async (_c) => { 32 | const response = await fetch('https://example.com') 33 | // clone the response to return a response with modifiable headers 34 | const newResponse = new Response(response.body, response) 35 | return newResponse 36 | }) 37 | ``` 38 | 39 | The headers of `Response` returned by `fetch` are immutable. So, an error will occur if you modify it. 40 | ::: 41 | -------------------------------------------------------------------------------- /examples/stripe-webhook.md: -------------------------------------------------------------------------------- 1 | # Stripe Webhook 2 | 3 | This introduces how to create an API with Hono to receive Stripe Webhook events. 4 | 5 | ## Preparation 6 | 7 | Please install the official Stripe SDK at first: 8 | 9 | ```bash 10 | npm install stripe 11 | ``` 12 | 13 | And put the following values on the `.dev.vars` file to insert the Stripe API keys: 14 | 15 | ``` 16 | STRIPE_API_KEY=sk_test_xxx 17 | STRIPE_WEBHOOK_SECRET=whsec_xxx 18 | ``` 19 | 20 | You can learn about the Stripe API keys by the following documents: 21 | 22 | - Secret Key: https://docs.stripe.com/keys 23 | - Webhook secret: https://docs.stripe.com/webhooks 24 | 25 | ## How to protect the API for Stripe Webhook events 26 | 27 | The API that processes webhook events is publicly accessible. Therefore, a mechanism is needed to protect it from attacks such as malicious third parties spoofing Stripe's webhook event objects and sending requests. In Stripe's case, you can protect the API by issuing a webhook secret and verifying each request. 28 | 29 | Learn more: https://docs.stripe.com/webhooks?lang=node#verify-official-libraries 30 | 31 | ## Implementing the Webhook API by hosting environment or framework 32 | 33 | To perform signature verification with Stripe, the raw request body is needed. 34 | When using a framework, you need to ensure that the original body is not modified. If any changes are made to the raw request body, the verification will fail. 35 | 36 | In the case of Hono, we can get the raw request body by the `context.req.text()` method. So we can create the webhook API like the following example: 37 | 38 | ```ts 39 | import Stripe from 'stripe' 40 | import { Hono } from 'hono' 41 | import { env } from 'hono/adapter' 42 | 43 | const app = new Hono() 44 | 45 | app.post('/webhook', async (context) => { 46 | const { STRIPE_SECRET_API_KEY, STRIPE_WEBHOOK_SECRET } = 47 | env(context) 48 | const stripe = new Stripe(STRIPE_SECRET_API_KEY) 49 | const signature = context.req.header('stripe-signature') 50 | try { 51 | if (!signature) { 52 | return context.text('', 400) 53 | } 54 | const body = await context.req.text() 55 | const event = await stripe.webhooks.constructEventAsync( 56 | body, 57 | signature, 58 | STRIPE_WEBHOOK_SECRET 59 | ) 60 | switch (event.type) { 61 | case 'payment_intent.created': { 62 | console.log(event.data.object) 63 | break 64 | } 65 | default: 66 | break 67 | } 68 | return context.text('', 200) 69 | } catch (err) { 70 | const errorMessage = `⚠️ Webhook signature verification failed. ${ 71 | err instanceof Error ? err.message : 'Internal server error' 72 | }` 73 | console.log(errorMessage) 74 | return context.text(errorMessage, 400) 75 | } 76 | }) 77 | 78 | export default app 79 | ``` 80 | 81 | ## See also 82 | 83 | - Details on Stripe Webhooks: 84 | https://docs.stripe.com/webhooks 85 | - Implementing for payment processing: 86 | https://docs.stripe.com/payments/handling-payment-events 87 | - Implementing for subscriptions: 88 | https://docs.stripe.com/billing/subscriptions/webhooks 89 | - List of webhook events sent by Stripe: 90 | https://docs.stripe.com/api/events 91 | - Sample template for Cloudflare: 92 | https://github.com/stripe-samples/stripe-node-cloudflare-worker-template/ 93 | -------------------------------------------------------------------------------- /examples/swagger-ui.md: -------------------------------------------------------------------------------- 1 | # Swagger UI 2 | 3 | [Swagger UI Middleware](https://github.com/honojs/middleware/tree/main/packages/swagger-ui) provides a middleware and a component for integrating [Swagger UI](https://swagger.io/docs/open-source-tools/swagger-ui/usage/installation/) with Hono applications. 4 | 5 | ```ts 6 | import { Hono } from 'hono' 7 | import { swaggerUI } from '@hono/swagger-ui' 8 | 9 | const app = new Hono() 10 | 11 | // Use the middleware to serve Swagger UI at /ui 12 | app.get('/ui', swaggerUI({ url: '/doc' })) 13 | 14 | export default app 15 | ``` 16 | 17 | ## See also 18 | 19 | - [Swagger UI Middleware](https://github.com/honojs/middleware/tree/main/packages/swagger-ui) 20 | -------------------------------------------------------------------------------- /examples/validator-error-handling.md: -------------------------------------------------------------------------------- 1 | # Error handling in Validator 2 | 3 | By using a validator, you can handle invalid input more easily. This example shows you can utilize the callback result for implementing custom error handling. 4 | 5 | Although this snippet employs [Zod Validator](https://github.com/honojs/middleware/blob/main/packages/zod-validator), you can apply a similar approach with any supported validator library. 6 | 7 | ```ts 8 | import { z } from 'zod' 9 | import { zValidator } from '@hono/zod-validator' 10 | 11 | const app = new Hono() 12 | 13 | const userSchema = z.object({ 14 | name: z.string(), 15 | age: z.number(), 16 | }) 17 | 18 | app.post( 19 | '/users/new', 20 | zValidator('json', userSchema, (result, c) => { 21 | if (!result.success) { 22 | return c.text('Invalid!', 400) 23 | } 24 | }), 25 | async (c) => { 26 | const user = c.req.valid('json') 27 | console.log(user.name) // string 28 | console.log(user.age) // number 29 | } 30 | ) 31 | ``` 32 | 33 | ## See also 34 | 35 | - [Zod Validator](https://github.com/honojs/middleware/blob/main/packages/zod-validator) 36 | - [Valibot Validator](https://github.com/honojs/middleware/tree/main/packages/valibot-validator) 37 | - [Typebox Validator](https://github.com/honojs/middleware/tree/main/packages/typebox-validator) 38 | - [Typia Validator](https://github.com/honojs/middleware/tree/main/packages/typia-validator) 39 | -------------------------------------------------------------------------------- /examples/web-api.md: -------------------------------------------------------------------------------- 1 | # Web API 2 | 3 | This is an example of making Web API on Cloudflare Workers and other runtimes. 4 | 5 | ```ts 6 | import { Hono } from 'hono' 7 | import { cors } from 'hono/cors' 8 | import { basicAuth } from 'hono/basic-auth' 9 | import { prettyJSON } from 'hono/pretty-json' 10 | import { getPosts, getPost, createPost, Post } from './model' 11 | 12 | const app = new Hono() 13 | app.get('/', (c) => c.text('Pretty Blog API')) 14 | app.use(prettyJSON()) 15 | app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404)) 16 | 17 | type Bindings = { 18 | USERNAME: string 19 | PASSWORD: string 20 | } 21 | 22 | const api = new Hono<{ Bindings: Bindings }>() 23 | api.use('/posts/*', cors()) 24 | 25 | api.get('/posts', (c) => { 26 | const { limit, offset } = c.req.query() 27 | const posts = getPosts({ limit, offset }) 28 | return c.json({ posts }) 29 | }) 30 | 31 | api.get('/posts/:id', (c) => { 32 | const id = c.req.param('id') 33 | const post = getPost({ id }) 34 | return c.json({ post }) 35 | }) 36 | 37 | api.post( 38 | '/posts', 39 | async (c, next) => { 40 | const auth = basicAuth({ 41 | username: c.env.USERNAME, 42 | password: c.env.PASSWORD, 43 | }) 44 | return auth(c, next) 45 | }, 46 | async (c) => { 47 | const post = await c.req.json() 48 | const ok = createPost({ post }) 49 | return c.json({ ok }) 50 | } 51 | ) 52 | 53 | app.route('/api', api) 54 | 55 | export default app 56 | ``` 57 | 58 | ## See also 59 | 60 | - [Hono Examples - basic](https://github.com/honojs/examples/tree/main/basic) 61 | -------------------------------------------------------------------------------- /examples/with-remix.md: -------------------------------------------------------------------------------- 1 | # Remix 2 | 3 | [Remix](https://remix.run/) is a Web Standards-based full-stack framework. 4 | 5 | Now, Remix and Hono can be used together through the fetch API. 6 | 7 | ## Remix + Hono 8 | 9 | You can use Remix as Hono middleware using [Remix + Hono](https://github.com/sergiodxa/remix-hono), like this: 10 | 11 | ```ts 12 | import * as build from '@remix-run/dev/server-build' 13 | import { remix } from 'remix-hono/handler' 14 | 15 | app.use('*', remix({ build, mode: process.env.NODE_ENV })) 16 | ``` 17 | 18 | ## See also 19 | 20 | - [Remix](https://remix.run/) 21 | - [Remix Hono](https://github.com/sergiodxa/remix-hono) 22 | -------------------------------------------------------------------------------- /examples/zod-openapi.md: -------------------------------------------------------------------------------- 1 | # Zod OpenAPI 2 | 3 | [Zod OpenAPI Hono](https://github.com/honojs/middleware/tree/main/packages/zod-openapi) is an extended Hono class that supports OpenAPI. 4 | With it, you can validate values and types using [Zod](https://zod.dev/) and generate OpenAPI Swagger documentation. On this website, only basic usage is shown. 5 | 6 | First, define your schemas with Zod. The `z` object should be imported from `@hono/zod-openapi`: 7 | 8 | ```ts 9 | import { z } from '@hono/zod-openapi' 10 | 11 | const ParamsSchema = z.object({ 12 | id: z 13 | .string() 14 | .min(3) 15 | .openapi({ 16 | param: { 17 | name: 'id', 18 | in: 'path', 19 | }, 20 | example: '1212121', 21 | }), 22 | }) 23 | 24 | const UserSchema = z 25 | .object({ 26 | id: z.string().openapi({ 27 | example: '123', 28 | }), 29 | name: z.string().openapi({ 30 | example: 'John Doe', 31 | }), 32 | age: z.number().openapi({ 33 | example: 42, 34 | }), 35 | }) 36 | .openapi('User') 37 | ``` 38 | 39 | Next, create a route: 40 | 41 | ```ts 42 | import { createRoute } from '@hono/zod-openapi' 43 | 44 | const route = createRoute({ 45 | method: 'get', 46 | path: '/users/{id}', 47 | request: { 48 | params: ParamsSchema, 49 | }, 50 | responses: { 51 | 200: { 52 | content: { 53 | 'application/json': { 54 | schema: UserSchema, 55 | }, 56 | }, 57 | description: 'Retrieve the user', 58 | }, 59 | }, 60 | }) 61 | ``` 62 | 63 | Finally, set up the app: 64 | 65 | ```ts 66 | import { OpenAPIHono } from '@hono/zod-openapi' 67 | 68 | const app = new OpenAPIHono() 69 | 70 | app.openapi(route, (c) => { 71 | const { id } = c.req.valid('param') 72 | return c.json({ 73 | id, 74 | age: 20, 75 | name: 'Ultra-man', 76 | }) 77 | }) 78 | 79 | // The OpenAPI documentation will be available at /doc 80 | app.doc('/doc', { 81 | openapi: '3.0.0', 82 | info: { 83 | version: '1.0.0', 84 | title: 'My API', 85 | }, 86 | }) 87 | ``` 88 | 89 | You can start your app just like you would with Hono. For Cloudflare Workers and Bun, use this entry point: 90 | 91 | ```ts 92 | export default app 93 | ``` 94 | 95 | ## See also 96 | 97 | - [Zod OpenAPI Hono](https://github.com/honojs/middleware/tree/main/packages/zod-openapi) 98 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hono - Web framework built on Web Standards 3 | titleTemplate: ':title' 4 | head: 5 | - [ 6 | 'meta', 7 | { 8 | property: 'og:description', 9 | content: 'Hono is a small, simple, and ultrafast web framework built on Web Standards. It works on Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Netlify, AWS Lambda, Lambda@Edge, and Node.js. Fast, but not only fast.', 10 | }, 11 | ] 12 | layout: home 13 | hero: 14 | name: Hono 15 | text: Web application framework 16 | tagline: Fast, lightweight, built on Web Standards. Support for any JavaScript runtime. 17 | image: 18 | src: /images/code.webp 19 | alt: "An example of code for Hono. \ 20 | import { Homo } from 'hono' \ 21 | const app = new Hono() \ 22 | app.get('/', (c) => c.text('Hello Hono!')) \ 23 | 24 | export default app" 25 | actions: 26 | - theme: brand 27 | text: Get Started 28 | link: /docs/ 29 | - theme: alt 30 | text: View on GitHub 31 | link: https://github.com/honojs/hono 32 | features: 33 | - icon: 🚀 34 | title: Ultrafast & Lightweight 35 | details: The router RegExpRouter is really fast. The hono/tiny preset is under 14kB. Using only Web Standard APIs. 36 | - icon: 🌍 37 | title: Multi-runtime 38 | details: Works on Cloudflare, Fastly, Deno, Bun, AWS, or Node.js. The same code runs on all platforms. 39 | - icon: 🔋 40 | title: Batteries Included 41 | details: Hono has built-in middleware, custom middleware, third-party middleware, and helpers. Batteries included. 42 | - icon: 😃 43 | title: Delightful DX 44 | details: Super clean APIs. First-class TypeScript support. Now, we've got "Types". 45 | --- 46 | 47 | 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hono.dev", 3 | "version": "3.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vitepress dev", 7 | "prebuild": "tsx ./scripts/build-llm-docs.ts", 8 | "build": "vitepress build", 9 | "preview": "vitepress preview", 10 | "format": "yarn prettier --check ./docs ./examples ./scripts index.md", 11 | "format:fix": "yarn prettier --check --write ./docs ./examples ./scripts index.md" 12 | }, 13 | "repository": "git@github.com:honojs/website.git", 14 | "author": "Yusuke Wada ", 15 | "private": true, 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@shikijs/vitepress-twoslash": "^1.23.0", 19 | "@types/node": "^22.13.1", 20 | "hono": "^4.6.3", 21 | "prettier": "^3.3.2", 22 | "tsx": "^4.19.2", 23 | "typescript": "^5.6.2", 24 | "vitepress": "1.6.3", 25 | "vitepress-plugin-group-icons": "^1.0.4", 26 | "vue": "^3.3.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /top / 301 2 | /snippets/top /snippets/ 301 3 | 4 | /api/* /docs/api/:splat 301 5 | /concepts/* /docs/concepts/:splat 301 6 | /getting-started/* /docs/getting-started/:splat 301 7 | /guides/* /docs/guides/:splat 301 8 | /helpers/* /docs/helpers/:splat 301 9 | /middleware/* /docs/middleware/:splat 301 10 | /snippets/* /examples/:splat 301 -------------------------------------------------------------------------------- /public/css/syntax.css: -------------------------------------------------------------------------------- 1 | /* Background */ .bg { background-color: #f0f3f3; } 2 | /* PreWrapper */ .chroma { background-color: #f0f3f3; } 3 | /* Other */ .chroma .x { } 4 | /* Error */ .chroma .err { color: #aa0000; background-color: #ffaaaa } 5 | /* CodeLine */ .chroma .cl { } 6 | /* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } 7 | /* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; } 8 | /* LineHighlight */ .chroma .hl { background-color: #ffffcc } 9 | /* LineNumbersTable */ .chroma .lnt { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } 10 | /* LineNumbers */ .chroma .ln { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } 11 | /* Line */ .chroma .line { display: flex; } 12 | /* Keyword */ .chroma .k { color: #006699; font-weight: bold } 13 | /* KeywordConstant */ .chroma .kc { color: #006699; font-weight: bold } 14 | /* KeywordDeclaration */ .chroma .kd { color: #006699; font-weight: bold } 15 | /* KeywordNamespace */ .chroma .kn { color: #006699; font-weight: bold } 16 | /* KeywordPseudo */ .chroma .kp { color: #006699 } 17 | /* KeywordReserved */ .chroma .kr { color: #006699; font-weight: bold } 18 | /* KeywordType */ .chroma .kt { color: #007788; font-weight: bold } 19 | /* Name */ .chroma .n { } 20 | /* NameAttribute */ .chroma .na { color: #330099 } 21 | /* NameBuiltin */ .chroma .nb { color: #336666 } 22 | /* NameBuiltinPseudo */ .chroma .bp { } 23 | /* NameClass */ .chroma .nc { color: #00aa88; font-weight: bold } 24 | /* NameConstant */ .chroma .no { color: #336600 } 25 | /* NameDecorator */ .chroma .nd { color: #9999ff } 26 | /* NameEntity */ .chroma .ni { color: #999999; font-weight: bold } 27 | /* NameException */ .chroma .ne { color: #cc0000; font-weight: bold } 28 | /* NameFunction */ .chroma .nf { color: #cc00ff } 29 | /* NameFunctionMagic */ .chroma .fm { } 30 | /* NameLabel */ .chroma .nl { color: #9999ff } 31 | /* NameNamespace */ .chroma .nn { color: #00ccff; font-weight: bold } 32 | /* NameOther */ .chroma .nx { } 33 | /* NameProperty */ .chroma .py { } 34 | /* NameTag */ .chroma .nt { color: #330099; font-weight: bold } 35 | /* NameVariable */ .chroma .nv { color: #003333 } 36 | /* NameVariableClass */ .chroma .vc { } 37 | /* NameVariableGlobal */ .chroma .vg { } 38 | /* NameVariableInstance */ .chroma .vi { } 39 | /* NameVariableMagic */ .chroma .vm { } 40 | /* Literal */ .chroma .l { } 41 | /* LiteralDate */ .chroma .ld { } 42 | /* LiteralString */ .chroma .s { color: #cc3300 } 43 | /* LiteralStringAffix */ .chroma .sa { color: #cc3300 } 44 | /* LiteralStringBacktick */ .chroma .sb { color: #cc3300 } 45 | /* LiteralStringChar */ .chroma .sc { color: #cc3300 } 46 | /* LiteralStringDelimiter */ .chroma .dl { color: #cc3300 } 47 | /* LiteralStringDoc */ .chroma .sd { color: #cc3300; font-style: italic } 48 | /* LiteralStringDouble */ .chroma .s2 { color: #cc3300 } 49 | /* LiteralStringEscape */ .chroma .se { color: #cc3300; font-weight: bold } 50 | /* LiteralStringHeredoc */ .chroma .sh { color: #cc3300 } 51 | /* LiteralStringInterpol */ .chroma .si { color: #aa0000 } 52 | /* LiteralStringOther */ .chroma .sx { color: #cc3300 } 53 | /* LiteralStringRegex */ .chroma .sr { color: #33aaaa } 54 | /* LiteralStringSingle */ .chroma .s1 { color: #cc3300 } 55 | /* LiteralStringSymbol */ .chroma .ss { color: #ffcc33 } 56 | /* LiteralNumber */ .chroma .m { color: #ff6600 } 57 | /* LiteralNumberBin */ .chroma .mb { color: #ff6600 } 58 | /* LiteralNumberFloat */ .chroma .mf { color: #ff6600 } 59 | /* LiteralNumberHex */ .chroma .mh { color: #ff6600 } 60 | /* LiteralNumberInteger */ .chroma .mi { color: #ff6600 } 61 | /* LiteralNumberIntegerLong */ .chroma .il { color: #ff6600 } 62 | /* LiteralNumberOct */ .chroma .mo { color: #ff6600 } 63 | /* Operator */ .chroma .o { color: #555555 } 64 | /* OperatorWord */ .chroma .ow { color: #000000; font-weight: bold } 65 | /* Punctuation */ .chroma .p { } 66 | /* Comment */ .chroma .c { color: #0099ff; font-style: italic } 67 | /* CommentHashbang */ .chroma .ch { color: #0099ff; font-style: italic } 68 | /* CommentMultiline */ .chroma .cm { color: #0099ff; font-style: italic } 69 | /* CommentSingle */ .chroma .c1 { color: #0099ff; font-style: italic } 70 | /* CommentSpecial */ .chroma .cs { color: #0099ff; font-weight: bold; font-style: italic } 71 | /* CommentPreproc */ .chroma .cp { color: #009999 } 72 | /* CommentPreprocFile */ .chroma .cpf { color: #009999 } 73 | /* Generic */ .chroma .g { } 74 | /* GenericDeleted */ .chroma .gd { background-color: #ffcccc } 75 | /* GenericEmph */ .chroma .ge { font-style: italic } 76 | /* GenericError */ .chroma .gr { color: #ff0000 } 77 | /* GenericHeading */ .chroma .gh { color: #003300; font-weight: bold } 78 | /* GenericInserted */ .chroma .gi { background-color: #ccffcc } 79 | /* GenericOutput */ .chroma .go { color: #aaaaaa } 80 | /* GenericPrompt */ .chroma .gp { color: #000099; font-weight: bold } 81 | /* GenericStrong */ .chroma .gs { font-weight: bold } 82 | /* GenericSubheading */ .chroma .gu { color: #003300; font-weight: bold } 83 | /* GenericTraceback */ .chroma .gt { color: #99cc66 } 84 | /* GenericUnderline */ .chroma .gl { text-decoration: underline } 85 | /* TextWhitespace */ .chroma .w { color: #bbbbbb } 86 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/favicon.ico -------------------------------------------------------------------------------- /public/images/bench01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench01.png -------------------------------------------------------------------------------- /public/images/bench02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench02.png -------------------------------------------------------------------------------- /public/images/bench03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench03.png -------------------------------------------------------------------------------- /public/images/bench04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench04.png -------------------------------------------------------------------------------- /public/images/bench05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench05.png -------------------------------------------------------------------------------- /public/images/bench06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench06.png -------------------------------------------------------------------------------- /public/images/bench07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench07.png -------------------------------------------------------------------------------- /public/images/bench08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench08.png -------------------------------------------------------------------------------- /public/images/bench09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench09.png -------------------------------------------------------------------------------- /public/images/bench10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench10.png -------------------------------------------------------------------------------- /public/images/bench11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench11.png -------------------------------------------------------------------------------- /public/images/bench12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench12.png -------------------------------------------------------------------------------- /public/images/bench13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench13.png -------------------------------------------------------------------------------- /public/images/bench14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench14.png -------------------------------------------------------------------------------- /public/images/bench15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench15.png -------------------------------------------------------------------------------- /public/images/bench16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/bench16.png -------------------------------------------------------------------------------- /public/images/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/code.png -------------------------------------------------------------------------------- /public/images/code.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/code.webp -------------------------------------------------------------------------------- /public/images/css-ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/css-ss.png -------------------------------------------------------------------------------- /public/images/hono-kawaii.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/hono-kawaii.png -------------------------------------------------------------------------------- /public/images/hono-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/hono-title.png -------------------------------------------------------------------------------- /public/images/logo-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/logo-large.png -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/logo.png -------------------------------------------------------------------------------- /public/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/images/onion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/onion.png -------------------------------------------------------------------------------- /public/images/pylon-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/pylon-example.png -------------------------------------------------------------------------------- /public/images/router-linear.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/router-linear.jpg -------------------------------------------------------------------------------- /public/images/router-regexp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/router-regexp.jpg -------------------------------------------------------------------------------- /public/images/router-tree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/router-tree.jpg -------------------------------------------------------------------------------- /public/images/sc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/sc.gif -------------------------------------------------------------------------------- /public/images/sc01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/sc01.gif -------------------------------------------------------------------------------- /public/images/sc02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/sc02.gif -------------------------------------------------------------------------------- /public/images/sc03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/sc03.gif -------------------------------------------------------------------------------- /public/images/sc04.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/sc04.gif -------------------------------------------------------------------------------- /public/images/ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/ss.png -------------------------------------------------------------------------------- /public/images/ss02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/ss02.png -------------------------------------------------------------------------------- /public/images/ss03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/ss03.png -------------------------------------------------------------------------------- /public/images/timing-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honojs/website/601c1670b18b9a006bc9c2aa423df3924b9ba896/public/images/timing-example.png -------------------------------------------------------------------------------- /scripts/build-llm-docs.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { glob } from 'node:fs/promises' 4 | 5 | const frontmatterRegex = /^\n*---(\n.+)*?\n---\n/ 6 | 7 | const docsDir = path.resolve('docs') 8 | 9 | const sliceExt = (file: string) => { 10 | return file.split('.').slice(0, -1).join('.') 11 | } 12 | 13 | const extractLabel = (file: string) => { 14 | return sliceExt(file.split('/').pop() || '') 15 | } 16 | 17 | function capitalizeDelimiter(str) { 18 | return str 19 | .split('-') 20 | .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) 21 | .join('-') 22 | } 23 | 24 | async function generateLLMDocs() { 25 | const outputListFile = path.resolve('public/llms.txt') 26 | 27 | const optionalFiles = await glob('**/*.md', { cwd: docsDir }) 28 | 29 | const optionals: string[] = [] 30 | 31 | for await (const file of optionalFiles) { 32 | optionals.push( 33 | `- [${capitalizeDelimiter(extractLabel(file)).replace(/-/, ' ')}](https://hono.dev/docs/${sliceExt(file)})` 34 | ) 35 | } 36 | 37 | fs.writeFileSync( 38 | outputListFile, 39 | [ 40 | '# Hono', 41 | '', 42 | '> 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.', 43 | '', 44 | '## Docs', 45 | '', 46 | '- [Full Docs](https://hono.dev/llms-full.txt) Full documentation of Hono. (without examples)', 47 | '- [Tiny Docs](https://hono.dev/llms-small.txt): Tiny documentation of Hono. (includes only desciption of core)', 48 | '', 49 | '## Examples', 50 | '', 51 | '- [Examples](https://github.com/honojs/website/tree/main/examples): List of example files.', 52 | '', 53 | '## Optional', 54 | '', 55 | ...optionals, 56 | ].join('\n'), 57 | 'utf-8' 58 | ) 59 | console.log(`< Output '${outputListFile}' `) 60 | 61 | const outputFullFile = path.resolve('public/llms-full.txt') 62 | const files = await glob('**/*.md', { cwd: docsDir }) 63 | 64 | const fullContent = await generateContent( 65 | files, 66 | docsDir, 67 | 'This is the full developer documentation for Hono.\n\n' 68 | ) 69 | 70 | fs.writeFileSync(outputFullFile, fullContent, 'utf-8') 71 | console.log(`< Output '${outputFullFile}' `) 72 | 73 | const outputTinyFile = path.resolve('public/llms-small.txt') 74 | 75 | const tinyExclude = ['concepts', 'helpers', 'middleware'] 76 | const tinyFiles = await glob('**/*.md', { 77 | cwd: docsDir, 78 | exclude: (filename: string) => tinyExclude.includes(filename), 79 | }) 80 | 81 | const tinyContent = await generateContent( 82 | tinyFiles, 83 | docsDir, 84 | 'This is the tiny developer documentation for Hono.\n\n' 85 | ) 86 | 87 | fs.writeFileSync(outputTinyFile, tinyContent, 'utf-8') 88 | console.log(`< Output '${outputTinyFile}' `) 89 | } 90 | 91 | async function generateContent( 92 | files: NodeJS.AsyncIterator, 93 | docsDir: string, 94 | header: string 95 | ): Promise { 96 | let content = header + '# Start of Hono documentation\n' 97 | 98 | for await (const file of files) { 99 | console.log(`> Writing '${file}' `) 100 | const fileContent = fs.readFileSync( 101 | path.resolve(docsDir, file), 102 | 'utf-8' 103 | ) 104 | content += fileContent.replace(frontmatterRegex, '') + '\n\n' 105 | } 106 | 107 | return content 108 | } 109 | 110 | generateLLMDocs().catch(console.error) 111 | --------------------------------------------------------------------------------