Security News
pnpm 10.0.0 Blocks Lifecycle Scripts by Default
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.
Hono is a small, simple, and fast web framework for building web applications and APIs in Node.js. It is designed to be lightweight and efficient, making it suitable for high-performance applications.
Basic Routing
Hono allows you to define routes for your web application. In this example, a basic GET route is defined that responds with 'Hello, Hono!' when accessed.
const { Hono } = require('hono');
const app = new Hono();
app.get('/', (c) => c.text('Hello, Hono!'));
app.listen(3000);
Middleware Support
Hono supports middleware, allowing you to execute code before your route handlers. This example demonstrates a simple logger middleware that logs the request method and URL.
const { Hono } = require('hono');
const app = new Hono();
const logger = (c, next) => {
console.log(`${c.req.method} ${c.req.url}`);
return next();
};
app.use(logger);
app.get('/', (c) => c.text('Hello, Hono!'));
app.listen(3000);
Error Handling
Hono provides a way to handle errors globally. In this example, an error is thrown in the route handler, and the global error handler responds with a 500 status code and a message.
const { Hono } = require('hono');
const app = new Hono();
app.get('/', (c) => {
throw new Error('Something went wrong!');
});
app.onError((err, c) => {
c.status(500);
return c.text('Internal Server Error');
});
app.listen(3000);
Express is a widely-used web framework for Node.js, known for its simplicity and flexibility. It offers a robust set of features for web and mobile applications, including routing, middleware support, and more. Compared to Hono, Express has a larger community and more extensive documentation, but Hono aims to be more lightweight and faster.
Koa is a web framework designed by the team behind Express. It aims to be a smaller, more expressive, and more robust foundation for web applications and APIs. Koa uses async functions to help eliminate callback hell and improve error handling. Compared to Hono, Koa is more modern and has a different approach to middleware, using a stack-like structure.
Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture. It is designed for high performance and low overhead. Compared to Hono, Fastify is more feature-rich and has a more extensive ecosystem, but Hono aims to be simpler and more lightweight.
Hono - [炎] means flame🔥 in Japanese - is a small, simple, and ultrafast web framework for Cloudflare Workers, Deno, Bun, and others.
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hono!!'))
app.fire()
Hono is fastest, compared to other routers for Cloudflare Workers.
hono - trie-router(default) x 389,510 ops/sec ±3.16% (85 runs sampled)
hono - regexp-router x 452,290 ops/sec ±2.64% (84 runs sampled)
itty-router x 206,013 ops/sec ±3.39% (90 runs sampled)
sunder x 323,131 ops/sec ±0.75% (97 runs sampled)
worktop x 191,218 ops/sec ±2.70% (91 runs sampled)
Fastest is hono - regexp-router
✨ Done in 43.56s.
autocannon -c 100 -d 40 -p 10 'http://127.0.0.1:8000/user/lookup/username/foo'
Hono is fastest, compared to other frameworks for Deno.
Framework | Version | Results |
---|---|---|
Hono - RegExpRouter | 1.6.0 | 5118k requests in 40.02s, 865 MB read |
Hono - TriRouter(default) | 1.6.0 | 4932k requests in 40.02s, 833 MB read |
Faster | 5.7 | 3579k requests in 40.02s, 551 MB read |
oak | 10.5.1 | 2385k requests in 40.02s, 403 MB read |
opine | 2.2.0 | 1491k requests in 40.02s, 346 MB read |
Another benchmark result: denosaurs/bench
Routers used in Hono are really smart.
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.
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 } from 'hono'
import { basicAuth } from 'hono/basic-auth'
const v1 = new Hono()
v1.get('/posts', (c) => {
return c.text('list posts')
})
.post(basicAuth({ username, password }), (c) => {
return c.text('created!', 201)
})
.get('/posts/:id', (c) => {
const id = c.req.param('id')
return c.text(`your id is ${id}`)
})
const app = new Hono()
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 hono
An instance of Hono
has these methods.
// HTTP Methods
app.get('/', (c) => c.text('GET /'))
app.post('/', (c) => c.text('POST /'))
app.put('/', (c) => c.text('PUT /'))
app.delete('/', (c) => c.text('DELETE /'))
// 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')
...
})
or all parameters at once:
app.get('/posts/:id/comment/:comment_id', (c) => {
const { id, comment_id } = c.req.param()
...
})
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
const { date, title } = c.req.param()
...
})
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, /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}`)
})
Group the routes with Hono
instance and add them to the main app with route
method.
const book = new Hono()
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
const app = new Hono()
app.route('/book', book)
Middleware works after/before Handler. We can get Request
before dispatching or manipulate Response
after dispatching.
Response
object. Only one handler will be called.await next()
The user can register middleware using c.use
or using c.HTTP_METHOD
as well as the handlers. For this feature, it's easy to specify the path and the method.
// match any method, all routes
app.use('*', logger())
// specify path
app.use('/posts/*', cors())
// specify method and path
app.post('/posts/*', basicAuth(), bodyParse())
If the handler returns Response
, it will be used for the end-user, and stopping the processing.
app.post('/posts', (c) => c.text('Created!', 201))
In this case, four middleware are processed before dispatching like this:
logger() -> cors() -> basicAuth() -> bodyParse() -> *handler*
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')
...
})
// Get all params at once
app.get('/search', (c) => {
const { q, limit, offset } = c.req.query()
...
})
// Multiple query values
app.get('/search', (c) => {
const queries = c.req.queries('q')
// ---> GET search?q=foo&q=bar
// queries[0] => foo, queries[1] => bar
...
})
// 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,
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('/', async (c, next) => {
await next()
c.res.headers.append('X-Debug', 'Debug message')
})
// ExecutionContext object
app.get('/foo', async (c) => {
c.executionCtx.waitUntil(
c.env.KV.put(key, data)
)
...
})
// FetchEvent object (only set when using Service Worker syntax)
app.get('/foo', async (c) => {
c.event.waitUntil(
c.env.KV.put(key, data)
)
...
})
Environment variables, secrets, and KV namespaces are known as bindings. Regardless of type, bindings are always available as global variables and can be accessed via the context c.env.BINDING_KEY
.
// 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, ctx: ExecutionContext) {
return app.fetch(request, env, ctx)
},
}
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)
})
The router
option specify which router is used inside. The default router is TrieRouter
. If you want to use RexExpRouter
, write like this:
import { RegExpRouter } from 'hono/router/reg-exp-router'
const app = new Hono({ router: new RegExpRouter() })
Handlers or middleware will be executed in registration order.
app.get('/book/a', (c) => c.text('a')) // a
app.get('/book/:slug', (c) => c.text('common')) // common
GET /book/a ---> `a`
GET /book/b ---> `common`
When a handler is executed, the process will be stopped.
app.get('*', (c) => c.text('common')) // common
app.get('/foo', (c) => c.text('foo')) // foo
GET /foo ---> `common` // foo will not be dispatched
If you have the middleware that you want to execute, write the code above the handler.
app.use('*', logger())
app.get('/foo', (c) => c.text('foo'))
If you want a "fallback" handler, write the code below the other handler.
app.get('/foo', (c) => c.text('foo')) // foo
app.get('*', (c) => c.text('fallback')) // fallback
GET /bar ---> `fallback`
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.
wrangler init
Initialize as a wrangler project.
mkdir hono-example
cd hono-example
npx wrangler init -y
npm install hono
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 skeleton, run this command.
npx create-cloudflare my-app https://github.com/honojs/hono-minimal
How about writing web API with Hono?
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { basicAuth } from 'hono/basic-auth'
import { prettyJSON } from 'hono/pretty-json'
import { getPosts, getPost, createPost, Post } from './model'
const app = new Hono()
app.get('/', (c) => c.text('Pretty Blog API'))
app.use('*', prettyJSON())
app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404))
export interface Bindings {
USERNAME: string
PASSWORD: string
}
const api = new Hono<Bindings>()
api.use('/posts/*', cors())
api.get('/posts', (c) => {
const { limit, offset } = c.req.query()
const posts = getPosts({ limit, offset })
return c.json({ posts })
})
api.get('/posts/:id', (c) => {
const id = c.req.param('id')
const post = getPost({ id })
return c.json({ post })
})
api.post(
'/posts',
async (c, next) => {
const auth = basicAuth({ username: c.env.USERNAME, password: c.env.PASSWORD })
await auth(c, next)
},
async (c) => {
const post = await c.req.json<Post>()
const ok = createPost({ post })
return c.json({ ok })
}
)
app.route('/api', api)
export default app
Hono also works on Deno. This feature is still experimental.
/** @jsx jsx */
import { serve } from 'https://deno.land/std/http/server.ts'
import { Hono, logger, poweredBy, serveStatic, jsx } from 'https://deno.land/x/hono/mod.ts'
const app = new Hono()
app.use('*', logger(), poweredBy())
app.get('/favicon.ico', serveStatic({ path: './public/favicon.ico' }))
app.get('/', (c) => {
return c.html(<h1>Hello Deno!</h1>)
})
serve(app.fetch)
Hono also works on Bun. This feature is still experimental.
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.json({ message: 'Hello Bun!' })
})
export default {
port: 3000,
fetch: app.fetch,
}
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.
Contributions Welcome! You can contribute in the following ways.
Thanks to all contributors! Especially, @metrue and @usualoma!
Yusuke Wada https://github.com/yusukebe
Distributed under the MIT License. See LICENSE for more information.
FAQs
Web framework built on Web Standards
We found that hono demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.
Product
Socket now supports uv.lock files to ensure consistent, secure dependency resolution for Python projects and enhance supply chain security.
Research
Security News
Socket researchers have discovered multiple malicious npm packages targeting Solana private keys, abusing Gmail to exfiltrate the data and drain Solana wallets.