| name | hono-patterns |
| description | This skill activates when writing Hono routes, handlers, middleware, or discussing Hono application architecture. It provides patterns for routing, middleware composition, error handling, and TypeScript integration. |
Hono Patterns
Core patterns for building Hono applications.
Routing Patterns
Modular Route Organization
Organize routes in separate files and compose with app.route():
import { Hono } from 'hono'
import type { Env } from '../types/bindings'
const users = new Hono<Env>()
users.get('/', (c) => c.json({ users: [] }))
users.get('/:id', (c) => c.json({ id: c.req.param('id') }))
users.post('/', (c) => c.json({ created: true }, 201))
export { users }
import { users } from './routes/users'
import { posts } from './routes/posts'
app.route('/api/users', users)
app.route('/api/posts', posts)
Route Groups with Shared Middleware
const api = new Hono<Env>()
api.use('*', authMiddleware)
api.route('/users', users)
api.route('/posts', posts)
app.route('/api', api)
app.get('/health', (c) => c.json({ status: 'ok' }))
Chained Route Definition (for RPC)
const routes = app
.get('/users', (c) => c.json({ users: [] }))
.post('/users', (c) => c.json({ created: true }, 201))
.get('/users/:id', (c) => c.json({ id: c.req.param('id') }))
export type AppType = typeof routes
Handler Patterns
Basic Handler
app.get('/users', async (c) => {
const users = await fetchUsers()
return c.json(users)
})
Handler with Validation
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
app.post('/users',
zValidator('json', z.object({
email: z.string().email(),
name: z.string().min(1)
})),
async (c) => {
const data = c.req.valid('json')
return c.json({ id: crypto.randomUUID(), ...data }, 201)
}
)
Factory Pattern for External Handlers
Use when handlers need to be defined outside route files:
import { createFactory } from 'hono/factory'
import type { Env } from '../types/bindings'
const factory = createFactory<Env>()
export const listUsers = factory.createHandlers(
zValidator('query', paginationSchema),
async (c) => {
const { page, limit } = c.req.valid('query')
return c.json({ users: [], page, limit })
}
)
users.get('/', ...listUsers)
Middleware Patterns
Creating Typed Middleware
import { createMiddleware } from 'hono/factory'
import type { Env } from '../types/bindings'
export const loggerMiddleware = createMiddleware<Env>(async (c, next) => {
const start = Date.now()
await next()
console.log(`${c.req.method} ${c.req.url} - ${Date.now() - start}ms`)
})
Middleware with Variables
type Env = {
Variables: {
user: { id: string; email: string }
}
}
const authMiddleware = createMiddleware<Env>(async (c, next) => {
const user = await validateToken(c.req.header('Authorization'))
c.set('user', user)
await next()
})
app.get('/profile', authMiddleware, (c) => {
const user = c.get('user')
return c.json(user)
})
Configurable Middleware Factory
interface CacheOptions {
maxAge: number
public?: boolean
}
export const cache = (options: CacheOptions) => {
return createMiddleware<Env>(async (c, next) => {
await next()
const directive = options.public ? 'public' : 'private'
c.header('Cache-Control', `${directive}, max-age=${options.maxAge}`)
})
}
app.get('/static/*', cache({ maxAge: 86400, public: true }))
Error Handling
HTTPException
import { HTTPException } from 'hono/http-exception'
app.get('/users/:id', async (c) => {
const user = await getUser(c.req.param('id'))
if (!user) {
throw new HTTPException(404, { message: 'User not found' })
}
return c.json(user)
})
Global Error Handler
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status)
}
console.error(err)
return c.json({ error: 'Internal server error' }, 500)
})
Custom Error Classes
class NotFoundError extends HTTPException {
constructor(resource: string) {
super(404, { message: `${resource} not found` })
}
}
class ValidationError extends HTTPException {
constructor(errors: Record<string, string>) {
super(400, { message: 'Validation failed', cause: errors })
}
}
Response Patterns
JSON Responses
c.json({ data: users })
c.json({ data: user }, 200)
c.json({ data: newUser }, 201)
c.json({ error: 'Not found' }, 404)
c.json({ error: 'Validation failed', details: errors }, 400)
c.body(null, 204)
Consistent Response Format
interface ApiResponse<T> {
data?: T
error?: string
meta?: {
page?: number
limit?: number
total?: number
}
}
function success<T>(c: Context, data: T, status = 200) {
return c.json({ data } as ApiResponse<T>, status)
}
function error(c: Context, message: string, status = 400) {
return c.json({ error: message } as ApiResponse<never>, status)
}
Context Access
Request Data
const id = c.req.param('id')
const { id, slug } = c.req.param()
const page = c.req.query('page')
const { page, limit } = c.req.query()
const auth = c.req.header('Authorization')
const data = c.req.valid('json')
const form = c.req.valid('form')
const query = c.req.valid('query')
Environment and Bindings
const secret = c.env.JWT_SECRET
const db = c.env.DB
const kv = c.env.KV
const bucket = c.env.BUCKET
Best Practices
- Always type your app:
new Hono<Env>()
- Validate all inputs with Zod
- Use
createMiddleware for type-safe middleware
- Export
AppType for RPC client
- Handle errors with HTTPException
- Use proper status codes: 200, 201, 204, 400, 401, 404, 500
- Organize routes in separate files
- Keep handlers small - delegate to services