Hono

English · 日本語

[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/honojs/hono/ci)](https://github.com/honojs/hono/actions) [![GitHub](https://img.shields.io/github/license/honojs/hono)](https://github.com/honojs/hono/blob/master/LICENSE) [![npm](https://img.shields.io/npm/v/hono)](https://www.npmjs.com/package/hono) [![npm](https://img.shields.io/npm/dm/hono)](https://www.npmjs.com/package/hono) [![npm type definitions](https://img.shields.io/npm/types/hono)](https://www.npmjs.com/package/hono) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/honojs/hono)](https://github.com/honojs/hono/pulse) [![GitHub last commit](https://img.shields.io/github/last-commit/honojs/hono)](https://github.com/honojs/hono/commits/master) Hono - _**[炎] means flame🔥 in Japanese**_ - is a small, simple, and ultrafast web framework for Cloudflare Workers or Service Worker based serverless such as Fastly Compute@Edge. ```ts import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Hono!!')) app.fire() ``` ## Features - **Ultrafast** - the router does not use linear loops. - **Zero-dependencies** - using only Service Worker and Web Standard API. - **Middleware** - built-in middleware and ability to extend with your own middleware. - **TypeScript** - first-class TypeScript support. - **Optimized** - for Cloudflare Workers. ## Benchmark **Hono is fastest**, compared to other routers for Cloudflare Workers. ```plain hono - trie-router(default) x 737,602 ops/sec ±3.65% (67 runs sampled) hono - regexp-router x 1,188,203 ops/sec ±6.42% (60 runs sampled) itty-router x 163,970 ops/sec ±3.05% (91 runs sampled) sunder x 344,468 ops/sec ±0.87% (97 runs sampled) worktop x 222,044 ops/sec ±2.13% (85 runs sampled) Fastest is hono - regexp-router ✨ Done in 84.04s. ``` ## Why so fast? Routers used in Hono are really smart. - **TrieRouter**(default) - Implemented with Trie tree structure. - **RegExpRouter** - Match routes with one big Regex made before dispatching at once. ## Hono in 1 minute A demonstration to create an application for Cloudflare Workers with Hono. ![Demo](https://user-images.githubusercontent.com/10682/151973526-342644f9-71c5-4fee-81f4-64a7558bb192.gif) ## Not only fast Hono is fast. But not only fast. ### Write Less, do more Built-in middleware make _"**Write Less, do more**"_ in reality. You can use a lot of middleware without writing code from scratch. Below are examples. - [Basic Authentication](https://github.com/honojs/hono/tree/master/src/middleware/basic-auth/) - [Cookie parsing / serializing](https://github.com/honojs/hono/tree/master/src/middleware/cookie/) - [CORS](https://github.com/honojs/hono/tree/master/src/middleware/cors/) - [ETag](https://github.com/honojs/hono/tree/master/src/middleware/etag/) - [GraphQL Server](https://github.com/honojs/hono/tree/master/src/middleware/graphql-server/) - [JWT Authentication](https://github.com/honojs/hono/tree/master/src/middleware/jwt/) - [Logger](https://github.com/honojs/hono/tree/master/src/middleware/logger/) - [Mustache template engine](https://github.com/honojs/hono/tree/master/src/middleware/mustache/) (Only for Cloudflare Workers) - [JSON pretty printing](https://github.com/honojs/hono/tree/master/src/middleware/pretty-json/) - [Serving static files](https://github.com/honojs/hono/tree/master/src/middleware/serve-static/) (Only for Cloudflare Workers) To enable logger and Etag middleware with just this code. ```ts import { Hono } from 'hono' import { etag } from 'hono/etag' import { logger } from 'hono/logger' const app = new Hono() app.use('*', etag(), (logger()) ``` And, the routing of Hono is so flexible. It's easy to construct large web applications. ```ts import { Hono, Route } from 'hono' import { cors } from 'hono/cors' const app = new Hono() const v1 = new Route() v1.get('/posts', (c) => { return c.text('list pots') }) .post('/posts', cors(), (c) => { return c.text('created!', 201) }) .get('/posts/:id', (c) => { const id = c.req.param('id') return c.text(`your id is ${id}`) }) app.route('/v1', v1) ``` ### Web Standard Request and Response object used in Hono are extensions of the Web Standard [Fetch API](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API). If you are familiar with that, you don't need to know more than that. ### Developer Experience Hono provides fine _"**Developer Experience**"_. Easy access to Request/Response thanks to the `Context` object. Above all, Hono is written in TypeScript. So, Hono has _"**Types**"_! For example, the named path parameters will be literal types. ![Demo](https://user-images.githubusercontent.com/10682/154179671-9e491597-6778-44ac-a8e6-4483d7ad5393.png) ## Install You can install Hono from the npm registry. ```sh npm install hono ``` ## Methods An instance of `Hono` has these methods. - app.**HTTP_METHOD**(\[path,\] handler|middleware...) - app.**all**(\[path,\] handler|middleware...) - app.**route**(path, \[Route\]) - app.**use**(\[path,\] middleware) - app.**notFound**(handler) - app.**onError**(err, handler) - app.**fire**() - app.**fetch**(request, env, event) - app.**request**(path, options) ## Routing ### Basic ```ts // HTTP Methods app.get('/', (c) => c.text('GET /')) app.post('/', (c) => c.text('POST /')) // Wildcard app.get('/wild/*/card', (c) => { return c.text('GET /wild/*/card') }) // Any HTTP methods app.all('/hello', (c) => c.text('Any Method /hello')) ``` ### Named Parameter ```ts app.get('/user/:name', (c) => { const name = c.req.param('name') ... }) ``` ### Regexp ```ts app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => { const date = c.req.param('date') const title = c.req.param('title') ... }) ``` ### Chained route ```ts app .get('/endpoint', (c) => { return c.text('GET /endpoint') }) .post((c) => { return c.text('POST /endpoint') }) .delete((c) => { return c.text('DELETE /endpoint') }) ``` ### no strict If `strict` is set false, `/hello`and`/hello/` are treated the same. ```ts const app = new Hono({ strict: false }) // Default is true app.get('/hello', (c) => c.text('/hello or /hello/')) ``` ### async/await ```js app.get('/fetch-url', async (c) => { const response = await fetch('https://example.com/') return c.text(`Status is ${response.status}`) }) ``` ## Route `Route` object enables Nested route. ```ts const book = new Route() book.get('/', (c) => c.text('List Books')) // GET /book book.get('/:id', (c) => { // GET /book/:id const id = c.req.param('id') return c.text('Get Book: ' + id) }) book.post('/', (c) => c.text('Create Book')) // POST /book app.route('/book', book) ``` ## Middleware Middleware operate after/before executing Handler. We can get `Response` before dispatching or manipulate `Response` after dispatching. ### Definition of Middleware - Handler - should return `Response` object. - Middleware - should return nothing, do `await next()` ### Built-in Middleware Hono has built-in middleware. ```ts import { Hono } from 'hono' import { poweredBy } from 'hono/powered-by' import { logger } from 'hono/logger' import { basicAuth } from 'hono/basicAuth' const app = new Hono() app.use('*', poweredBy()) app.use('*', logger()) app.use( '/auth/*', basicAuth({ username: 'hono', password: 'acoolproject', }) ) ``` Available built-in middleware is listed on [src/middleware](https://github.com/honojs/hono/tree/master/src/middleware). ### Custom Middleware You can write your own middleware. ```ts // Custom logger app.use('*', async (c, next) => { console.log(`[${c.req.method}] ${c.req.url}`) await next() }) // Add a custom header app.use('/message/*', async (c, next) => { await next() c.header('x-message', 'This is middleware!') }) app.get('/message/hello', (c) => c.text('Hello Middleware!')) ``` ## Not Found `app.notFound` for customizing Not Found Response. ```js app.notFound((c) => { return c.text('Custom 404 Message', 404) }) ``` ## Error Handling `app.onError` handle the error and return the customized Response. ```js app.onError((err, c) => { console.error(`${err}`) return c.text('Custom Error Message', 500) }) ``` ## Context To handle Request and Response, you can use `Context` object. ### c.req ```ts // Get Request object app.get('/hello', (c) => { const userAgent = c.req.headers.get('User-Agent') ... }) // Shortcut to get a header value app.get('/shortcut', (c) => { const userAgent = c.req.header('User-Agent') ... }) // Query params app.get('/search', (c) => { const query = c.req.query('q') ... }) // Captured params app.get('/entry/:id', (c) => { const id = c.req.param('id') ... }) ``` ### Shortcuts for Response ```ts app.get('/welcome', (c) => { // Set headers c.header('X-Message', 'Hello!') c.header('Content-Type', 'text/plain') // Set HTTP status code c.status(201) // Return the response body return c.body('Thank you for comming') }) ``` The Response is the same as below. ```ts new Response('Thank you for comming', { status: 201, statusText: 'Created', headers: { 'X-Message': 'Hello', 'Content-Type': 'text/plain', }, }) ``` ### c.text() Render text as `Content-Type:text/plain`. ```ts app.get('/say', (c) => { return c.text('Hello!') }) ``` ### c.json() Render JSON as `Content-Type:application/json`. ```ts app.get('/api', (c) => { return c.json({ message: 'Hello!' }) }) ``` ### c.html() Render HTML as `Content-Type:text/html`. ```ts app.get('/', (c) => { return c.html('

Hello! Hono!

') }) ``` ### c.notFound() Return the `Not Found` Response. ```ts app.get('/notfound', (c) => { return c.notFound() }) ``` ### c.redirect() Redirect, default status code is `302`. ```ts app.get('/redirect', (c) => c.redirect('/')) app.get('/redirect-permanently', (c) => c.redirect('/', 301)) ``` ### c.res ```ts // Response object app.use('/', (c, next) => { next() c.res.headers.append('X-Debug', 'Debug message') }) ``` ### c.event ```ts // FetchEvent object app.use('*', async (c, next) => { c.event.waitUntil( ... ) await next() }) ``` ### c.env ```ts // Environment object for Cloudflare Workers app.get('*', async c => { const counter = c.env.COUNTER ... }) ``` ## fire `app.fire()` do this. ```ts addEventListener('fetch', (event) => { event.respondWith(this.handleEvent(event)) }) ``` ## fetch `app.fetch` for Cloudflare Module Worker syntax. ```ts export default { fetch(request: Request, env: Env, event: FetchEvent) { return app.fetch(request, env, event) }, } ``` or just do: ```ts export default app ``` ## request `request` is a useful method for testing. ```js test('GET /hello is ok', async () => { const res = await app.request('http://localhost/hello') expect(res.status).toBe(200) }) ``` ## Cloudflare Workers with Hono Using [Wrangler](https://developers.cloudflare.com/workers/cli-wrangler/), you can develop the application locally and publish it with few commands. Let's write your first code for Cloudflare Workers with Hono. ### 1. `wrangler init` Initialize as a wrangler project. ``` mkdir hono-example cd hono-example npx wrangler init -y ``` ### 2. `npm install hono` Install `hono` from the npm registry. ``` npm init -y npm i hono ``` ### 3. Write your app Edit `src/index.ts`. Only 4 lines!! ```ts // src/index.ts import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Hello! Hono!')) app.fire() ``` ### 4. Run Run the development server locally. Then, access `http://127.0.0.1:8787/` in your Web browser. ``` npx wrangler dev ``` ### 5. Publish Deploy to Cloudflare. That's all! ``` npx wrangler publish index.ts ``` ## Starter template You can start making your Cloudflare Workers application with [the starter template](https://github.com/honojs/hono-minimal). It is really minimal using TypeScript, esbuild, Miniflare, and Jest. To generate a project skelton, run this command. ``` npx create-cloudflare my-app https://github.com/honojs/hono-minimal ``` ## Examples - Hono Examples - ## Related projects Implementation of the original router `TrieRouter` is inspired by [goblin](https://github.com/bmf-san/goblin). `RegExpRouter` is inspired by [Router::Boom](https://github.com/tokuhirom/Router-Boom). API design is inspired by [express](https://github.com/expressjs/express) and [koa](https://github.com/koajs/koa). [itty-router](https://github.com/kwhitley/itty-router), [Sunder](https://github.com/SunderJS/sunder), and [worktop](https://github.com/lukeed/worktop) are the other routers or frameworks for Cloudflare Workers. - express - - koa - - itty-router - - Sunder - - goblin - - worktop - - Router::Boom - ## Contributing Contributions Welcome! You can contribute in the following ways. - Write or fix documents - Write code of middleware - Fix bugs - Refactor the code - etc. ## Contributors Thanks to [all contributors](https://github.com/honojs/hono/graphs/contributors)! Especially, [@metrue](https://github.com/metrue) and [@usualoma](https://github.com/usualoma)! ## Author Yusuke Wada ## License Distributed under the MIT License. See [LICENSE](LICENSE) for more information.