Code & Development

Hono.js: The Web Framework You're Probably Not Using Yet — But Should Be

Express was built in 2010. Hono has zero dependencies, runs on any runtime, and processes 4-5x more requests. Here is why the JavaScript ecosystem is migrating.

Hono.js: The Web Framework You're Probably Not Using Yet — But Should Be

Express was released in 2010. Back then, TypeScript wasn't a mainstream language, edge computing was science fiction, and Cloudflare Workers wasn't even on the horizon.

The JavaScript ecosystem of 2026 is a different world. We have Bun, Deno, Cloudflare Workers, AWS Lambda Edge — each with its own runtime quirks. And we have Hono: a framework built for this world.

2.8 million weekly downloads at the end of 2025 — 340% growth in one year. That's not hype. It's real adoption from teams that tested it and stayed.

Bundle size
12kB
zero external dependencies
Cloudflare Workers
100k+
requests per second
vs Express
4-5×
faster on Node.js

What Hono is

Hono (炎) means flame in Japanese. It was created by Yusuke Wada with a specific goal: a web framework built on Web Standards that runs on any JavaScript runtime — no adapters, no workarounds.

The core difference from Express: Hono uses the native web platform APIs — Request, Response, fetch. Express uses Node.js's http module, which doesn't exist in other runtimes. That design decision is what makes Hono genuinely multi-runtime.

Hono Web Standards Cloudflare Workers AWS Lambda Node.js Bun Deno Vercel Edge
Diagram — Hono runs on any JavaScript runtime

Getting started

Hono's API will feel familiar if you've used Express before. The difference shows up in the details — and the details are everything.

JS
# For Node.jsnpm create hono@latest my-app# Select: nodejs# For Cloudflare Workersnpm create cloudflare@latest my-app -- --template=hono# For Bunbun create hono my-app
JS
import { Hono } from 'hono'const app = new Hono()app.get('/', (c) => c.text('Hono!'))app.get('/json', (c) => c.json({ message: 'hello' }))app.post('/echo', async (c) => {  const body = await c.req.json()  return c.json(body)})export default app

Routing and route grouping

Hono's routing system uses a trie-based RegExpRouter — no linear loops. That's what guarantees performance even with hundreds of registered routes.

JS
import { Hono } from 'hono'const app = new Hono()// Route parametersapp.get('/posts/:id', (c) => {  const id = c.req.param('id')  return c.json({ id })})// Query stringsapp.get('/search', (c) => {  const q = c.req.query('q')  const page = c.req.query('page') ?? '1'  return c.json({ q, page })})// Grouping with basePathconst api = new Hono().basePath('/api')// Sub-routers per resourceconst users = new Hono()users.get('/', (c) => c.json([]))users.post('/', async (c) => {  const body = await c.req.json()  return c.json(body, 201)})users.get('/:id', (c) => c.json({ id: c.req.param('id') }))// Mount on the main appapi.route('/users', users)export default api

Middleware

Hono ships built-in middleware for the most common cases — no external packages required. CORS, logger, bearer auth, cache, compression — all available in the main package.

JS
import { Hono } from 'hono'import { cors } from 'hono/cors'import { logger } from 'hono/logger'import { bearerAuth } from 'hono/bearer-auth'import { cache } from 'hono/cache'const app = new Hono()// Global middlewareapp.use('*', logger())app.use('*', cors({  origin: ['https://mysite.com', 'http://localhost:3000'],  allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],}))// Route-level middlewareconst token = 'my-secret-token'app.use('/admin/*', bearerAuth({ token }))// Cache on Cloudflare Workersapp.use('/static/*', cache({  cacheName: 'static-assets',  cacheControl: 'max-age=3600',}))// Custom middlewareapp.use('*', async (c, next) => {  const start = Date.now()  await next()  const ms = Date.now() - start  c.header('X-Response-Time', `${ms}ms`)})app.get('/', (c) => c.text('OK'))export default app

Zod validation — the real differentiator

The Zod integration is where Hono shows the difference in developer experience. Define the schema once and get automatic request validation, parsing and TypeScript type inference — all in one shot.

JS
import { Hono } from 'hono'import { z } from 'zod'import { zValidator } from '@hono/zod-validator'const app = new Hono()// User creation schemaconst createUserSchema = z.object({  name: z.string().min(2).max(100),  email: z.string().email(),  age: z.number().int().min(18).optional(),})// Query params schemaconst paginationSchema = z.object({  page: z.coerce.number().int().positive().default(1),  limit: z.coerce.number().int().min(1).max(100).default(20),})app.post(  '/users',  zValidator('json', createUserSchema),  (c) => {    const { name, email, age } = c.req.valid('json')    // name, email, age are fully typed here    // TypeScript knows exactly what they are    return c.json({ id: 1, name, email, age }, 201)  })app.get(  '/users',  zValidator('query', paginationSchema),  (c) => {    const { page, limit } = c.req.valid('query')    // page and limit are already numbers, not strings    return c.json({ page, limit, total: 0, data: [] })  })export default app

RPC Client — end-to-end type safety

This is the feature that surprises people coming from Express the most. Hono ships a built-in RPC client that generates a typed client for calling API routes directly from the frontend — no tRPC, no codegen, no extra configuration.

JS
// server.ts — export the route typesimport { Hono } from 'hono'import { z } from 'zod'import { zValidator } from '@hono/zod-validator'const app = new Hono()const route = app  .get('/api/posts', (c) => c.json([{ id: 1, title: 'Hello' }]))  .post(    '/api/posts',    zValidator('json', z.object({ title: z.string() })),    async (c) => {      const { title } = c.req.valid('json')      return c.json({ id: 2, title }, 201)    }  )export type AppType = typeof routeexport default app
JS
// client.ts — frontend with full type safetyimport { hc } from 'hono/client'import type { AppType } from './server'const client = hc<AppType>('http://localhost:8787')// TypeScript knows the return type of each routeconst posts = await client.api.posts.$get()const data = await posts.json()// data is typed as { id: number; title: string }[]// Compile error if the payload is wrongconst newPost = await client.api.posts.$post({  json: { title: 'New Post' }, // TypeScript validates here})
Frontend hc<AppType>(url) client.api.posts.$get() Hono Server export type AppType = typeof route TypeScript validates payload infers return type HTTP types
Diagram — How the RPC Client works

Native JSX and SSR

Hono supports JSX natively for server-side rendering — no React required, no bundler configuration. Useful for simple pages, transactional emails, or any dynamic HTML.

JS
// app.tsximport { Hono } from 'hono'import type { FC } from 'hono/jsx'const app = new Hono()const Layout: FC = ({ children }) => (  <html lang="en">    <head>      <meta charset="UTF-8" />      <title>My Site</title>    </head>    <body>{children}</body>  </html>)const PostCard: FC<{ title: string; summary: string }> = ({ title, summary }) => (  <article>    <h2>{title}</h2>    <p>{summary}</p>  </article>)app.get('/posts', (c) => {  const posts = [    { title: 'Post 1', summary: 'Summary 1' },    { title: 'Post 2', summary: 'Summary 2' },  ]  return c.html(    <Layout>      <h1>Blog</h1>      {posts.map((p) => <PostCard {...p} />)}    </Layout>  )})export default app

Deploy: same application, different runtimes

Portability is Hono's most important strategic advantage. The same application code runs on any runtime — only the adapter changes.

Node.js

import { serve } from '@hono/node-server' import app from './app' serve({ fetch: app.fetch, port: 3000, }) // package.json // "start": "node dist/server.js"

Cloudflare Workers

import app from './app' // Cloudflare Workers uses // the default export directly export default app // wrangler.toml // name = "my-app" // main = "src/index.ts"

Performance: what the benchmarks say

Express uses Node.js's http module with 13 years of accumulated synchronous middleware overhead. Hono is async-native, uses the Fetch API internally, and has an optimized radix trie router. Less work per request.

Requests / second (higher = better) Hono (Bun) ~850k req/s Hono (Cloudflare Workers) ~100k req/s Hono (Node.js) ~350k req/s Express ~80k req/s
Diagram — Requests per second (comparative benchmark)

When to use it and when not to

Hono isn't a silver bullet. The choice between it and Express depends on context.

✅ Use Hono when

Greenfield project Edge deployment (Cloudflare Workers) Need multi-runtime (Node + Bun + Deno) TypeScript is a priority Bundle size matters API gateway or performance proxy Small team, no Express legacy

⚠️ Stick with Express when

Legacy codebase with 50+ Express middleware Team already trained and productive in Express Depends on Express-specific libraries Migration cost exceeds the benefit No edge deployment plans

Express isn't going anywhere. It sustains millions of production applications and will keep getting maintained.

But the next project doesn't need to start in 2010.