Skip to Content
overviewPhilosophy

Last Updated: 3/9/2026


Philosophy and Design Principles

Understanding Hono’s philosophy helps you make the most of the framework and understand why it’s designed the way it is.

The Origin Story

At first, the goal was simple: create a web application on Cloudflare Workers. But there was no good framework that worked on Cloudflare Workers at the time.

So Hono was born.

What started as a learning exercise to build a router using Trie trees evolved into something much more ambitious. A friend contributed an ultra-fast router called “RegExpRouter.” Another friend created Basic authentication middleware.

By using only Web Standard APIs, Hono could run on Deno and Bun without any changes. When people asked “is there Express for Bun?”, the answer became: “no, but there is Hono.”

The ecosystem grew organically. GraphQL servers, Firebase authentication, Sentry middleware, and a Node.js adapter emerged from the community.

Hono became damn fast, made many things possible, and worked everywhere.

Hono could become the Standard for Web Standards.

Core Principles

1. Web Standards First

Principle: Build on the Web Platform, not against it.

Hono uses Web Standard APIs exclusively:

  • Request and Response objects
  • Headers API
  • URL and URLSearchParams
  • ReadableStream for streaming
  • fetch for HTTP requests

Why it matters:

// This is a Web Standard Response // It works in browsers, workers, Node.js, Deno, Bun app.get('/', (c) => { return new Response('Hello', { status: 200, headers: { 'Content-Type': 'text/plain' } }) }) // Hono just makes it easier app.get('/', (c) => c.text('Hello'))

By embracing Web Standards:

  • Future-proof: Standards evolve slowly and deliberately
  • Portable: Code runs anywhere that implements the standards
  • Familiar: If you know web APIs, you know Hono
  • Interoperable: Easy to integrate with other standard-compliant tools

See also: Web Standards Concept

2. Performance Without Compromise

Principle: Speed is a feature, not a trade-off.

Hono doesn’t sacrifice developer experience for performance, or vice versa. Both are essential.

Fast routing:

// RegExpRouter converts all routes to a single regex // O(1) lookup time regardless of route count app.get('/users/:id', handler) app.get('/posts/:id/comments/:commentId', handler) // ... hundreds more routes // Still O(1) lookup!

Small bundle:

// Tree-shakeable - only bundle what you use import { Hono } from 'hono' import { cors } from 'hono/cors' import { logger } from 'hono/logger' // Only these imports are bundled

Why it matters:

  • Edge computing: Milliseconds matter when you’re close to users
  • Serverless: Faster = cheaper
  • User experience: Speed is the ultimate feature

See also: Performance Benchmarks

3. Developer Happiness

Principle: Make the right thing the easy thing.

Good APIs feel intuitive. Great APIs feel obvious.

Intuitive context object:

app.get('/hello/:name', (c) => { // Everything you need is on `c` const name = c.req.param('name') const userAgent = c.req.header('User-Agent') c.header('X-Custom', 'value') return c.json({ message: `Hello ${name}` }) })

Type inference that just works:

app.get('/posts/:id', (c) => { const id = c.req.param('id') // ^? string - TypeScript knows! })

Helpful error messages:

// Hono tells you exactly what's wrong app.get('/posts/:id', (c) => { return c.req.param('slug') // Error: Parameter 'slug' does not exist })

4. Composability Over Configuration

Principle: Build complex systems from simple, composable pieces.

Hono favors composition over configuration. There are no config files, no magic, no hidden behavior.

Middleware composition:

// Compose middleware like LEGO blocks app.use(logger()) app.use('/api/*', cors()) app.use('/admin/*', basicAuth({ username: 'admin', password: 'secret' })) app.get('/api/data', (c) => c.json({ data }))

Route composition:

// Build sub-applications and compose them const api = new Hono() .get('/users', getUsers) .post('/users', createUser) const app = new Hono() .route('/api/v1', api) .route('/api/v2', apiV2)

Custom middleware:

// Creating middleware is just a function const timing = async (c, next) => { const start = Date.now() await next() const ms = Date.now() - start c.header('X-Response-Time', `${ms}ms`) } app.use(timing)

5. Convention Over Rigidity

Principle: Provide good defaults, but don’t force them.

Hono has conventions that make common tasks easy, but you’re never locked in.

Convention:

// The conventional way - clean and simple app.get('/', (c) => c.text('Hello'))

Flexibility:

// But you can always drop to Web Standards app.get('/', (c) => { return new Response('Hello', { status: 200, headers: new Headers({ 'Content-Type': 'text/plain' }) }) })

Convention:

// Use built-in validation import { zValidator } from '@hono/zod-validator' app.post('/posts', zValidator('json', schema), handler)

Flexibility:

// Or roll your own import { validator } from 'hono/validator' app.post('/posts', validator('json', (value, c) => { // Custom validation logic if (!isValid(value)) return c.text('Invalid', 400) return value }), handler)

6. Runtime Agnostic

Principle: Don’t play favorites with runtimes.

Hono treats all runtimes equally. The core works everywhere. Platform-specific features are opt-in.

Core (works everywhere):

import { Hono } from 'hono' import { cors } from 'hono/cors' const app = new Hono() app.use(cors()) app.get('/', (c) => c.text('Hello'))

Platform-specific (opt-in):

// Only import what you need for your platform import { upgradeWebSocket } from 'hono/cloudflare-workers' import { serveStatic } from 'hono/bun'

Why it matters:

  • No vendor lock-in: Switch platforms without rewriting code
  • Future-proof: New runtimes are automatically supported
  • Best tool for the job: Choose runtime based on requirements, not framework limitations

Design Decisions

Why No Class-Based Controllers?

Many frameworks use class-based controllers:

// NOT Hono's style class UserController { @Get('/users/:id') async getUser(@Param('id') id: string) { // ... } }

Hono uses functions instead:

// Hono's style app.get('/users/:id', async (c) => { const id = c.req.param('id') // ... })

Why?

  • Simpler: No decorators, no class instantiation, no dependency injection
  • Composable: Functions compose naturally
  • Portable: No build-time transformations required
  • Debuggable: Stack traces are clearer

Why Context Object Instead of Separate req/res?

Some frameworks separate request and response:

// NOT Hono's style app.get('/', (req, res) => { res.send('Hello') })

Hono unifies them in a context object:

// Hono's style app.get('/', (c) => { return c.text('Hello') })

Why?

  • Extensible: Easy to add custom properties with c.set() and c.get()
  • Type-safe: Context can be typed with generics
  • Middleware-friendly: Pass one object through the chain
  • Clearer: Everything related to this request/response is in one place

Why Return Responses Instead of Mutating?

Hono favors returning responses over mutating a response object:

// Hono's style - functional app.get('/', (c) => { return c.json({ message: 'Hello' }) }) // Also works - imperative app.get('/', (c) => { c.status(200) c.header('Content-Type', 'application/json') return c.body(JSON.stringify({ message: 'Hello' })) })

Why prefer the first style?

  • Predictable: Clear what the handler returns
  • Testable: Easy to test return values
  • Composable: Can transform responses functionally
  • Explicit: No hidden state mutations

What’s Next?