Hono[炎] - means flame🔥 in Japanese - is small, simple, and ultrafast web framework for Cloudflare Workers and 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.
- Optimized - for Cloudflare Workers.
Hono is fastest, compared to other routers for Cloudflare Workers.
hono x 809,503 ops/sec ±6.94% (73 runs sampled)
itty-router x 157,310 ops/sec ±4.31% (87 runs sampled)
sunder x 328,350 ops/sec ±2.30% (95 runs sampled)
worktop x 209,758 ops/sec ±4.28% (83 runs sampled)
Fastest is hono
✨ Done in 60.66s.
A demonstration to create an application for Cloudflare Workers with Hono.
Now, the named path parameter has types.
You can install Hono from the npm registry.
$ yarn add hono
or
$ npm install hono
An instance of Hono
has these methods.
- app.HTTP_METHOD(path, handler)
- app.all(path, handler)
- app.route(path)
- app.use(path, middleware)
- app.notFound(handler)
- app.onError(err, handler)
- app.fire()
- app.fetch(request, env, event)
- app.request(path, option)
// 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')
...
})
const book = app.route('/book')
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
If strict
is set false, /hello
and/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}`)
})
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 Reponse, 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',
'Content-Length': '22',
},
})
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 app
*/
request
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 or Miniflare, you can develop the application locally and publish it with few commands.
Let's write your first code for Cloudflare Workers with Hono.
Wrangler 1.x does not support importing middleware. We recommend two ways:
- Use Wragler 2.0 Beta.
- Build without webpack 4.x. For example, you can use esbuild. See the starter template.
Make a npm skeleton directory.
$ mkdir hono-example
$ cd hono-example
$ npm init -y
Initialize as a wrangler project.
$ npx wrangler@beta init
Answer the questions. If you want, you can answer y
.
Would you like to install wrangler into your package.json? (y/n) <--- n
Would you like to use TypeScript? (y/n) <--- n
Would you like to create a Worker at src/index.js? (y/n) <--- n
Install hono
from the npm registry.
$ npm i hono
Only 4 lines!!
// index.js
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@beta dev index.js
Deploy to Cloudflare. That's all!
$ npx wrangler@beta publish index.js
You can start making your Cloudflare Workers application with the starter template. It is really minimal using TypeScript, esbuild, and Miniflare.
To generate a project skelton, run this command.
$ wrangler generate my-app https://github.com/yusukebe/hono-minimal
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 by the following way.
- Write or fix documents
- Write code of middleware
- Fix bugs
- Refactor the code
- etc.
Let's make Hono together!
Thanks to all contributors!
Yusuke Wada https://github.com/yusukebe
Distributed under the MIT License. See LICENSE for more information.