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:
RequestandResponseobjectsHeadersAPIURLandURLSearchParamsReadableStreamfor streamingfetchfor 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 bundledWhy 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()andc.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?
- See it in action: Quick Start
- Understand the foundation: Web Standards
- Learn the patterns: Middleware Concept