Last Updated: 3/9/2026
Basic Concepts
Understand the core concepts that power Hono applications: requests, responses, middleware, and the application lifecycle.
Prerequisites
Before reading this, complete Hello World to understand basic routing.
The Request/Response Cycle
Every Hono application follows this flow:
Request → Middleware → Handler → Response- Request arrives from the client
- Middleware processes the request (optional)
- Handler generates the response
- Response returns to the client
Handlers vs. Middleware
Understanding the difference is crucial:
Handler
A handler returns a Response object. Only one handler executes per request.
app.get('/hello', (c) => {
return c.text('Hello!') // Returns Response
})Middleware
A middleware calls await next() to pass control to the next middleware or handler.
app.use(async (c, next) => {
console.log('Before handler')
await next() // Continue to next middleware/handler
console.log('After handler')
})Middleware can also return a Response to short-circuit the chain:
app.use('/admin/*', async (c, next) => {
const token = c.req.header('Authorization')
if (!token) {
return c.text('Unauthorized', 401) // Stop here
}
await next() // Continue if authorized
})The Context Object
The Context object (c) is your interface to everything:
Request Access
app.get('/example', (c) => {
// Headers
const userAgent = c.req.header('User-Agent')
// Path parameters
const id = c.req.param('id')
// Query parameters
const page = c.req.query('page')
// Request body (async)
const body = await c.req.json()
return c.text('OK')
})Response Generation
app.get('/example', (c) => {
// Set status code
c.status(201)
// Set headers
c.header('X-Custom', 'value')
// Return response
return c.json({ ok: true })
})Variables
Store and retrieve values within a request:
app.use(async (c, next) => {
c.set('startTime', Date.now())
await next()
})
app.get('/', (c) => {
const startTime = c.get('startTime')
const duration = Date.now() - startTime
return c.text(`Request took ${duration}ms`)
})Learn more in Context API.
Middleware Execution Order
Middleware executes in registration order:
app.use(async (c, next) => {
console.log('1: start')
await next()
console.log('1: end')
})
app.use(async (c, next) => {
console.log('2: start')
await next()
console.log('2: end')
})
app.get('/', (c) => {
console.log('handler')
return c.text('Hello')
})Output:
1: start
2: start
handler
2: end
1: endThis “onion” pattern lets you wrap handlers with pre/post processing.
Using Built-in Middleware
Hono includes useful middleware out of the box:
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { basicAuth } from 'hono/basic-auth'
const app = new Hono()
// Apply to all routes
app.use(logger())
app.use(cors())
// Apply to specific paths
app.use('/admin/*', basicAuth({
username: 'admin',
password: 'secret',
}))
app.get('/admin/dashboard', (c) => {
return c.text('Admin Dashboard')
})See all built-in middleware in the Reference.
Route Matching
Routes are matched in registration order:
app.get('/posts/new', (c) => c.text('New post form'))
app.get('/posts/:id', (c) => c.text('Post detail'))Visit /posts/new → “New post form” (first match wins)
Wildcards
app.get('/posts/*', (c) => c.text('Any post path'))Matches /posts/, /posts/123, /posts/123/comments, etc.
Method Matching
// Match any HTTP method
app.all('/api/*', (c) => {
c.header('X-API-Version', '1.0')
return c.text('API')
})
// Match specific methods
app.on(['PUT', 'DELETE'], '/posts/:id', (c) => {
return c.text('Update or delete')
})Grouping Routes
Organize related routes:
const api = new Hono()
api.get('/users', (c) => c.json({ users: [] }))
api.post('/users', (c) => c.json({ created: true }, 201))
const app = new Hono()
app.route('/api', api) // Mount at /apiNow /api/users is available.
Learn more in Building Larger Applications.
Error Handling
Handle errors globally:
app.onError((err, c) => {
console.error(`Error: ${err.message}`)
return c.text('Internal Server Error', 500)
})
app.get('/error', (c) => {
throw new Error('Something went wrong!')
})Not Found Handler
Customize 404 responses:
app.notFound((c) => {
return c.text('Custom 404 Not Found', 404)
})Testing Your App
Test without starting a server:
// In your test file
const res = await app.request('/hello')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Hello!')Learn more in the Testing Guide.
What’s Next
Understand middleware: Read the Middleware Guide to learn about built-in and custom middleware.
Learn routing: Check out the Routing Guide for advanced patterns.
Add validation: See the Validation Guide to validate requests with Zod.
Deploy your app: Explore Deployment guides for your platform.