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.
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hono!!'))
app.fire()- 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.
Hono is fastest, compared to other routers for Cloudflare Workers.
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.
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.
A demonstration to create an application for Cloudflare Workers with Hono.
Hono is fast. But not only fast.
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
- Cookie parsing / serializing
- CORS
- ETag
- GraphQL Server
- JWT Authentication
- Logger
- Mustache template engine (Only for Cloudflare Workers)
- JSON pretty printing
- Serving static files (Only for Cloudflare Workers)
To enable logger and Etag middleware with just this code.
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.
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)Request and Response object used in Hono are extensions of the Web Standard Fetch API. If you are familiar with that, you don't need to know more than that.
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.
You can install Hono from the npm registry.
npm install honoAn 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)
// 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'))app.get('/user/:name', (c) => {
const name = c.req.param('name')
...
})app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
const date = c.req.param('date')
const title = c.req.param('title')
...
})app
.get('/endpoint', (c) => {
return c.text('GET /endpoint')
})
.post((c) => {
return c.text('POST /endpoint')
})
.delete((c) => {
return c.text('DELETE /endpoint')
})If strict is set false, /helloand/hello/ are treated the same.
const app = new Hono({ strict: false }) // Default is true
app.get('/hello', (c) => c.text('/hello or /hello/'))app.get('/fetch-url', async (c) => {
const response = await fetch('https://example.com/')
return c.text(`Status is ${response.status}`)
})Route object enables Nested route.
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 operate after/before executing Handler. We can get Response before dispatching or manipulate Response after dispatching.
- Handler - should return
Responseobject. - Middleware - should return nothing, do
await next()
Hono has built-in middleware.
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.
You can write your own middleware.
// 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!'))app.notFound for customizing Not Found Response.
app.notFound((c) => {
return c.text('Custom 404 Message', 404)
})app.onError handle the error and return the customized Response.
app.onError((err, c) => {
console.error(`${err}`)
return c.text('Custom Error Message', 500)
})To handle Request and Response, you can use Context object.
// 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')
...
})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.
new Response('Thank you for comming', {
status: 201,
statusText: 'Created',
headers: {
'X-Message': 'Hello',
'Content-Type': 'text/plain',
},
})Render text as Content-Type:text/plain.
app.get('/say', (c) => {
return c.text('Hello!')
})Render JSON as Content-Type:application/json.
app.get('/api', (c) => {
return c.json({ message: 'Hello!' })
})Render HTML as Content-Type:text/html.
app.get('/', (c) => {
return c.html('<h1>Hello! Hono!</h1>')
})Return the Not Found Response.
app.get('/notfound', (c) => {
return c.notFound()
})Redirect, default status code is 302.
app.get('/redirect', (c) => c.redirect('/'))
app.get('/redirect-permanently', (c) => c.redirect('/', 301))// Response object
app.use('/', (c, next) => {
next()
c.res.headers.append('X-Debug', 'Debug message')
})// FetchEvent object
app.use('*', async (c, next) => {
c.event.waitUntil(
...
)
await next()
})// Environment object for Cloudflare Workers
app.get('*', async c => {
const counter = c.env.COUNTER
...
})app.fire() do this.
addEventListener('fetch', (event) => {
event.respondWith(this.handleEvent(event))
})app.fetch for Cloudflare Module Worker syntax.
export default {
fetch(request: Request, env: Env, event: FetchEvent) {
return app.fetch(request, env, event)
},
}or just do:
export default apprequest is a useful method for testing.
test('GET /hello is ok', async () => {
const res = await app.request('http://localhost/hello')
expect(res.status).toBe(200)
})Using Wrangler, you can develop the application locally and publish it with few commands.
Let's write your first code for Cloudflare Workers with Hono.
Initialize as a wrangler project.
mkdir hono-example
cd hono-example
npx wrangler init -y
Install hono from the npm registry.
npm init -y
npm i hono
Edit src/index.ts. Only 4 lines!!
// src/index.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello! Hono!'))
app.fire()Run the development server locally. Then, access http://127.0.0.1:8787/ in your Web browser.
npx wrangler dev
Deploy to Cloudflare. That's all!
npx wrangler publish ./src/index.ts
You can start making your Cloudflare Workers application with the starter template. 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
- Hono Examples - https://github.com/honojs/examples
Implementation of the original router TrieRouter is inspired by goblin. RegExpRouter is inspired by Router::Boom. API design is inspired by express and koa. itty-router, Sunder, and worktop are the other routers or frameworks for Cloudflare Workers.
- express - https://github.com/expressjs/express
- koa - https://github.com/koajs/koa
- itty-router - https://github.com/kwhitley/itty-router
- Sunder - https://github.com/SunderJS/sunder
- goblin - https://github.com/bmf-san/goblin
- worktop - https://github.com/lukeed/worktop
- Router::Boom - https://github.com/tokuhirom/Router-Boom
Contributions Welcome! You can contribute in the following ways.
- Write or fix documents
- Write code of middleware
- Fix bugs
- Refactor the code
- etc.
Thanks to all contributors! Especially, @metrue and @usualoma!
Yusuke Wada https://github.com/yusukebe
Distributed under the MIT License. See LICENSE for more information.