This is the tiny developer documentation for Hono. # Start of Hono documentation # Hono Hono - _**means flameπŸ”₯ in Japanese**_ - is a small, simple, and ultrafast web framework built on Web Standards. It works on any JavaScript runtime: Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Netlify, AWS Lambda, Lambda@Edge, and Node.js. Fast, but not only fast. ```ts twoslash import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Hono!')) export default app ``` ## Quick Start Just run this: ::: code-group ```sh [npm] npm create hono@latest ``` ```sh [yarn] yarn create hono ``` ```sh [pnpm] pnpm create hono@latest ``` ```sh [bun] bun create hono@latest ``` ```sh [deno] deno init --npm hono@latest ``` ::: ## Features - **Ultrafast** πŸš€ - The router `RegExpRouter` is really fast. Not using linear loops. Fast. - **Lightweight** πŸͺΆ - The `hono/tiny` preset is under 14kB. Hono has zero dependencies and uses only the Web Standards. - **Multi-runtime** 🌍 - Works on Cloudflare Workers, Fastly Compute, Deno, Bun, AWS Lambda, or Node.js. The same code runs on all platforms. - **Batteries Included** πŸ”‹ - Hono has built-in middleware, custom middleware, third-party middleware, and helpers. Batteries included. - **Delightful DX** πŸ˜ƒ - Super clean APIs. First-class TypeScript support. Now, we've got "Types". ## Use-cases Hono is a simple web application framework similar to Express, without a frontend. But it runs on CDN Edges and allows you to construct larger applications when combined with middleware. Here are some examples of use-cases. - Building Web APIs - Proxy of backend servers - Front of CDN - Edge application - Base server for a library - Full-stack application ## Who is using Hono? | Project | Platform | What for? | | ---------------------------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------- | | [cdnjs](https://cdnjs.com) | Cloudflare Workers | A free and open-source CDN service. _Hono is used for the API server_. | | [Cloudflare D1](https://www.cloudflare.com/developer-platform/d1/) | Cloudflare Workers | Serverless SQL databases. _Hono is used for the internal API server_. | | [Cloudflare Workers KV](https://www.cloudflare.com/developer-platform/workers-kv/) | Cloudflare Workers | Serverless key-value database. _Hono is used for the internal API server_. | | [BaseAI](https://baseai.dev) | Local AI Server | Serverless AI agent pipes with memory. An open-source agentic AI framework for web. _API server with Hono_. | | [Unkey](https://unkey.dev) | Cloudflare Workers | An open-source API authentication and authorization. _Hono is used for the API server_. | | [OpenStatus](https://openstatus.dev) | Bun | An open-source website & API monitoring platform. _Hono is used for the API server_. | | [Deno Benchmarks](https://deno.com/benchmarks) | Deno | A secure TypeScript runtime built on V8. _Hono is used for benchmarking_. | | [Clerk](https://clerk.com) | Cloudflare Workers | An open-source User Management Platform. _Hono is used for the API server_. | And the following. - [Drivly](https://driv.ly/) - Cloudflare Workers - [repeat.dev](https://repeat.dev/) - Cloudflare Workers Do you want to see more? See [Who is using Hono in production?](https://github.com/orgs/honojs/discussions/1510). ## Hono in 1 minute A demonstration to create an application for Cloudflare Workers with Hono. ![A gif showing a hono app being created quickly with fast iteration.](/images/sc.gif) ## Ultrafast **Hono is the fastest**, compared to other routers for Cloudflare Workers. ``` Hono x 402,820 ops/sec Β±4.78% (80 runs sampled) itty-router x 212,598 ops/sec Β±3.11% (87 runs sampled) sunder x 297,036 ops/sec Β±4.76% (77 runs sampled) worktop x 197,345 ops/sec Β±2.40% (88 runs sampled) Fastest is Hono ✨ Done in 28.06s. ``` See [more benchmarks](/docs/concepts/benchmarks). ## Lightweight **Hono is so small**. With the `hono/tiny` preset, its size is **under 14KB** when minified. There are many middleware and adapters, but they are bundled only when used. For context, the size of Express is 572KB. ``` $ npx wrangler dev --minify ./src/index.ts ⛅️ wrangler 2.20.0 -------------------- ⬣ Listening at http://0.0.0.0:8787 - http://127.0.0.1:8787 - http://192.168.128.165:8787 Total Upload: 11.47 KiB / gzip: 4.34 KiB ``` ## Multiple routers **Hono has multiple routers**. **RegExpRouter** is the fastest router in the JavaScript world. It matches the route using a single large Regex created before dispatch. With **SmartRouter**, it supports all route patterns. **LinearRouter** registers the routes very quickly, so it's suitable for an environment that initializes applications every time. **PatternRouter** simply adds and matches the pattern, making it small. See [more information about routes](/docs/concepts/routers). ## Web Standards Thanks to the use of the **Web Standards**, Hono works on a lot of platforms. - Cloudflare Workers - Cloudflare Pages - Fastly Compute - Deno - Bun - Vercel - AWS Lambda - Lambda@Edge - Others And by using [a Node.js adapter](https://github.com/honojs/node-server), Hono works on Node.js. See [more information about Web Standards](/docs/concepts/web-standard). ## Middleware & Helpers **Hono has many middleware and helpers**. This makes "Write Less, do more" a reality. Out of the box, Hono provides middleware and helpers for: - [Basic Authentication](/docs/middleware/builtin/basic-auth) - [Bearer Authentication](/docs/middleware/builtin/bearer-auth) - [Body Limit](/docs/middleware/builtin/body-limit) - [Cache](/docs/middleware/builtin/cache) - [Compress](/docs/middleware/builtin/compress) - [Context Storage](/docs/middleware/builtin/context-storage) - [Cookie](/docs/helpers/cookie) - [CORS](/docs/middleware/builtin/cors) - [ETag](/docs/middleware/builtin/etag) - [html](/docs/helpers/html) - [JSX](/docs/guides/jsx) - [JWT Authentication](/docs/middleware/builtin/jwt) - [Logger](/docs/middleware/builtin/logger) - [Language](/docs/middleware/builtin/language) - [Pretty JSON](/docs/middleware/builtin/pretty-json) - [Secure Headers](/docs/middleware/builtin/secure-headers) - [SSG](/docs/helpers/ssg) - [Streaming](/docs/helpers/streaming) - [GraphQL Server](https://github.com/honojs/middleware/tree/main/packages/graphql-server) - [Firebase Authentication](https://github.com/honojs/middleware/tree/main/packages/firebase-auth) - [Sentry](https://github.com/honojs/middleware/tree/main/packages/sentry) - Others! For example, adding ETag and request logging only takes a few lines of code with Hono: ```ts import { Hono } from 'hono' import { etag } from 'hono/etag' import { logger } from 'hono/logger' const app = new Hono() app.use(etag(), logger()) ``` See [more information about Middleware](/docs/concepts/middleware). ## Developer Experience Hono provides a delightful "**Developer Experience**". Easy access to Request/Response thanks to the `Context` object. Moreover, Hono is written in TypeScript. Hono has "**Types**". For example, the path parameters will be literal types. ![A screenshot showing Hono having proper literal typing when URL parameters. The URL "/entry/:date/:id" allows for request parameters to be "date" or "id"](/images/ss.png) And, the Validator and Hono Client `hc` enable the RPC mode. In RPC mode, you can use your favorite validator such as Zod and easily share server-side API specs with the client and build type-safe applications. See [Hono Stacks](/docs/concepts/stacks). # Examples See the [Examples section](/examples/). # Helpers Helpers are available to assist in developing your application. Unlike middleware, they don't act as handlers, but rather provide useful functions. For instance, here's how to use the [Cookie helper](/docs/helpers/cookie): ```ts import { getCookie, setCookie } from 'hono/cookie' const app = new Hono() app.get('/cookie', (c) => { const yummyCookie = getCookie(c, 'yummy_cookie') // ... setCookie(c, 'delicious_cookie', 'macha') // }) ``` ## Available Helpers - [Accepts](/docs/helpers/accepts) - [Adapter](/docs/helpers/adapter) - [Cookie](/docs/helpers/cookie) - [css](/docs/helpers/css) - [Dev](/docs/helpers/dev) - [Factory](/docs/helpers/factory) - [html](/docs/helpers/html) - [JWT](/docs/helpers/jwt) - [SSG](/docs/helpers/ssg) - [Streaming](/docs/helpers/streaming) - [Testing](/docs/helpers/testing) - [WebSocket](/docs/helpers/websocket) # Testing [Vitest]: https://vitest.dev/ Testing is important. In actuality, it is easy to test Hono's applications. The way to create a test environment differs from each runtime, but the basic steps are the same. In this section, let's test with Cloudflare Workers and [Vitest]. ::: tip 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. ::: ## Request and Response All you need to do is create a Request and pass it to the Hono application to validate the Response. You can then use the useful `app.request` method. ::: tip For a typed test client see the [testing helper](/docs/helpers/testing). ::: For example, consider an application that provides the following REST API. ```ts app.get('/posts', (c) => { return c.text('Many posts') }) app.post('/posts', (c) => { return c.json( { message: 'Created', }, 201, { 'X-Custom': 'Thank you', } ) }) ``` Make a request to `GET /posts` and test the response. ```ts describe('Example', () => { test('GET /posts', async () => { const res = await app.request('/posts') expect(res.status).toBe(200) expect(await res.text()).toBe('Many posts') }) }) ``` To make a request to `POST /posts`, do the following. ```ts test('POST /posts', async () => { const res = await app.request('/posts', { method: 'POST', }) expect(res.status).toBe(201) expect(res.headers.get('X-Custom')).toBe('Thank you') expect(await res.json()).toEqual({ message: 'Created', }) }) ``` To make a request to `POST /posts` with `JSON` data, do the following. ```ts test('POST /posts', async () => { const res = await app.request('/posts', { method: 'POST', body: JSON.stringify({ message: 'hello hono' }), headers: new Headers({ 'Content-Type': 'application/json' }), }) expect(res.status).toBe(201) expect(res.headers.get('X-Custom')).toBe('Thank you') expect(await res.json()).toEqual({ message: 'Created', }) }) ``` To make a request to `POST /posts` with `multipart/form-data` data, do the following. ```ts test('POST /posts', async () => { const formData = new FormData() formData.append('message', 'hello') const res = await app.request('/posts', { method: 'POST', body: formData, }) expect(res.status).toBe(201) expect(res.headers.get('X-Custom')).toBe('Thank you') expect(await res.json()).toEqual({ message: 'Created', }) }) ``` You can also pass an instance of the Request class. ```ts test('POST /posts', async () => { const req = new Request('http://localhost/posts', { method: 'POST', }) const res = await app.request(req) expect(res.status).toBe(201) expect(res.headers.get('X-Custom')).toBe('Thank you') expect(await res.json()).toEqual({ message: 'Created', }) }) ``` In this way, you can test it as like an End-to-End. ## Env 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): ```ts const MOCK_ENV = { API_HOST: 'example.com', DB: { prepare: () => { /* mocked D1 */ }, }, } test('GET /posts', async () => { const res = await app.request('/posts', {}, MOCK_ENV) }) ``` # Best Practices Hono is very flexible. You can write your app as you like. However, there are best practices that are better to follow. ## Don't make "Controllers" when possible When possible, you should not create "Ruby on Rails-like Controllers". ```ts // πŸ™ // A RoR-like Controller const booksList = (c: Context) => { return c.json('list books') } app.get('/books', booksList) ``` The issue is related to types. For example, the path parameter cannot be inferred in the Controller without writing complex generics. ```ts // πŸ™ // A RoR-like Controller const bookPermalink = (c: Context) => { const id = c.req.param('id') // Can't infer the path param return c.json(`get ${id}`) } ``` Therefore, you don't need to create RoR-like controllers and should write handlers directly after path definitions. ```ts // πŸ˜ƒ app.get('/books/:id', (c) => { const id = c.req.param('id') // Can infer the path param return c.json(`get ${id}`) }) ``` ## `factory.createHandlers()` in `hono/factory` If you still want to create a RoR-like Controller, use `factory.createHandlers()` in [`hono/factory`](/docs/helpers/factory). If you use this, type inference will work correctly. ```ts import { createFactory } from 'hono/factory' import { logger } from 'hono/logger' // ... // πŸ˜ƒ const factory = createFactory() const middleware = factory.createMiddleware(async (c, next) => { c.set('foo', 'bar') await next() }) const handlers = factory.createHandlers(logger(), middleware, (c) => { return c.json(c.var.foo) }) app.get('/api', ...handlers) ``` ## Building a larger application Use `app.route()` to build a larger application without creating "Ruby on Rails-like Controllers". If your application has `/authors` and `/books` endpoints and you wish to separate files from `index.ts`, create `authors.ts` and `books.ts`. ```ts // authors.ts import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.json('list authors')) app.post('/', (c) => c.json('create an author', 201)) app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`)) export default app ``` ```ts // books.ts import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.json('list books')) app.post('/', (c) => c.json('create a book', 201)) app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`)) export default app ``` Then, import them and mount on the paths `/authors` and `/books` with `app.route()`. ```ts // index.ts import { Hono } from 'hono' import authors from './authors' import books from './books' const app = new Hono() // πŸ˜ƒ app.route('/authors', authors) app.route('/books', books) export default app ``` ### If you want to use RPC features The code above works well for normal use cases. However, if you want to use the `RPC` feature, you can get the correct type by chaining as follows. ```ts // authors.ts import { Hono } from 'hono' const app = new Hono() .get('/', (c) => c.json('list authors')) .post('/', (c) => c.json('create an author', 201)) .get('/:id', (c) => c.json(`get ${c.req.param('id')}`)) export default app export type AppType = typeof app ``` If you pass the type of the `app` to `hc`, it will get the correct type. ```ts import type { AppType } from './authors' import { hc } from 'hono/client' // πŸ˜ƒ const client = hc('http://localhost') // Typed correctly ``` For more detailed information, please see [the RPC page](/docs/guides/rpc#using-rpc-with-larger-applications). ## HEAD Request Best Practices ### Understanding Hono's HEAD Handling Hono automatically handles HEAD requests by converting them to GET requests and stripping the response body. This behavior is built into the framework's dispatch layer and happens before route matching occurs. ### βœ… Do: Use GET Routes for HEAD Requests ```typescript // GOOD: This GET route automatically handles HEAD requests app.get('/api/users', async (c) => { const users = await getUsers() c.header('X-Total-Count', users.length.toString()) return c.json(users) }) // HEAD /api/users will return: // - Same headers as GET (including X-Total-Count) // - Status 200 // - No body (null) ``` ### βœ… Do: Use Middleware for HEAD-Specific Logic ```typescript // GOOD: Use middleware when HEAD needs different behavior app.use('/api/resource', async (c, next) => { await next() // Add HEAD-specific headers after the handler if (c.req.method === 'HEAD') { c.header('X-HEAD-Processed', 'true') // Don't compute expensive body content for HEAD c.res = new Response(null, c.res) } }) ``` ### ❌ Don't: Try to Create Dedicated HEAD Handlers ```typescript // BAD: This won't work as expected app.head('/api/users', (c) => { // This handler will NEVER be called c.header('X-Custom', 'value') return c.text('ignored') }) // BAD: Using on() also won't work app.on('HEAD', '/api/users', (c) => { // Still converted to GET before route matching }) ``` ### Performance Considerations - **Avoid expensive operations in GET handlers if you expect many HEAD requests**: Use middleware to detect HEAD and skip body generation - **Cache headers work identically**: HEAD responses respect the same caching rules as GET - **Middleware compatibility**: Most middleware works with HEAD, but body-processing middleware (like compression) automatically skips HEAD requests ### Testing HEAD Requests ```typescript // Always test both GET and HEAD responses it('handles HEAD requests correctly', async () => { const getRes = await app.request('/api/users') const headRes = await app.request('/api/users', { method: 'HEAD' }) expect(headRes.status).toBe(getRes.status) expect(headRes.headers.get('X-Total-Count')).toBe( getRes.headers.get('X-Total-Count') ) expect(headRes.body).toBe(null) }) ``` ### Notes - The automatic HEAD conversion ensures consistent headers between GET and HEAD responses - This behavior is consistent across all Hono runtimes (Cloudflare Workers, Deno, Bun, Node.js) - If you need completely different logic for HEAD vs GET, consider using different endpoints rather than trying to override the framework's HEAD handling # Client Components `hono/jsx` supports not only server side but also client side. This means that it is possible to create an interactive UI that runs in the browser. We call it Client Components or `hono/jsx/dom`. It is fast and very small. The counter program in `hono/jsx/dom` is only 2.8KB with Brotli compression, but 47.8KB for React. This section introduces Client Components-specific features. ## Counter example Here is an example of a simple counter, the same code works as in React. ```tsx import { useState } from 'hono/jsx' import { render } from 'hono/jsx/dom' function Counter() { const [count, setCount] = useState(0) return (

Count: {count}

) } function App() { return ( ) } const root = document.getElementById('root') render(, root) ``` ## `render()` You can use `render()` to insert JSX components within a specified HTML element. ```tsx render(, container) ``` You can see full example code here: [Counter example](https://github.com/honojs/examples/tree/main/hono-vite-jsx). ## Hooks compatible with React hono/jsx/dom has Hooks that are compatible or partially compatible with React. You can learn about these APIs by looking at [the React documentation](https://react.dev/reference/react/hooks). - `useState()` - `useEffect()` - `useRef()` - `useCallback()` - `use()` - `startTransition()` - `useTransition()` - `useDeferredValue()` - `useMemo()` - `useLayoutEffect()` - `useReducer()` - `useDebugValue()` - `createElement()` - `memo()` - `isValidElement()` - `useId()` - `createRef()` - `forwardRef()` - `useImperativeHandle()` - `useSyncExternalStore()` - `useInsertionEffect()` - `useFormStatus()` - `useActionState()` - `useOptimistic()` ## `startViewTransition()` family The `startViewTransition()` family contains original hooks and functions to handle [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) easily. The examples below demonstrate how to use them. ### 1. A very simple example You can write a transition using the `document.startViewTransition` shortly with the `startViewTransition()`. ```tsx import { useState, startViewTransition } from 'hono/jsx' import { css, Style } from 'hono/css' export default function App() { const [showLargeImage, setShowLargeImage] = useState(false) return ( <>