hono-patterns
Scannednpx machina-cli add skill smicolon/ai-kit/hono-patterns --openclawFiles (1)
SKILL.md
6.4 KB
Hono Patterns
Core patterns for building Hono applications.
Routing Patterns
Modular Route Organization
Organize routes in separate files and compose with app.route():
// routes/users.ts
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 }
// index.ts
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>()
// Apply auth to all /api routes
api.use('*', authMiddleware)
api.route('/users', users)
api.route('/posts', posts)
app.route('/api', api)
// Public routes remain unprotected
app.get('/health', (c) => c.json({ status: 'ok' }))
Chained Route Definition (for RPC)
// Chain routes for proper type inference
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>()
// Define handler with proper types
export const listUsers = factory.createHandlers(
zValidator('query', paginationSchema),
async (c) => {
const { page, limit } = c.req.valid('query')
return c.json({ users: [], page, limit })
}
)
// Use in routes
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
// Update types/bindings.ts
type Env = {
Variables: {
user: { id: string; email: string }
}
}
// Middleware sets variable
const authMiddleware = createMiddleware<Env>(async (c, next) => {
const user = await validateToken(c.req.header('Authorization'))
c.set('user', user) // Type-safe!
await next()
})
// Handler accesses variable
app.get('/profile', authMiddleware, (c) => {
const user = c.get('user') // Type-safe!
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}`)
})
}
// Usage
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
// Success
c.json({ data: users })
c.json({ data: user }, 200)
c.json({ data: newUser }, 201)
// Errors
c.json({ error: 'Not found' }, 404)
c.json({ error: 'Validation failed', details: errors }, 400)
// No content
c.body(null, 204)
Consistent Response Format
interface ApiResponse<T> {
data?: T
error?: string
meta?: {
page?: number
limit?: number
total?: number
}
}
// Helper
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
// Path params
const id = c.req.param('id')
const { id, slug } = c.req.param()
// Query params
const page = c.req.query('page')
const { page, limit } = c.req.query()
// Headers
const auth = c.req.header('Authorization')
// Body (validated)
const data = c.req.valid('json')
const form = c.req.valid('form')
const query = c.req.valid('query')
Environment and Bindings
// Environment variables
const secret = c.env.JWT_SECRET
// Cloudflare bindings
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
createMiddlewarefor type-safe middleware - Export
AppTypefor 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
Source
git clone https://github.com/smicolon/ai-kit/blob/main/packs/hono/skills/hono-patterns/SKILL.mdView on GitHub Overview
Hono patterns provide core templates for building scalable applications. It covers modular routing, shared middleware, error handling, and TypeScript integration.
How This Skill Works
Routes are defined in modules and mounted with app.route. Middleware can be shared across routes via api.use or composed inline. TypeScript integration is supported via hono factory patterns and zod validators.
When to Use It
- Organize routes in separate files and compose with app.route
- Apply auth or other middleware to a group of /api routes
- Define RPC like chained routes to preserve type inference
- Validate inputs with zod validators in handlers
- Define external handlers with a factory for reusable, typed logic
Quick Start
- Step 1: Split routes into modules (e.g., routes/users.ts exporting users) and import them where you mount the API
- Step 2: Use app.route to assemble the API surface and apply shared middleware with api.use or route stacking
- Step 3: Introduce validation and factories (zValidator, createFactory) and mount typed handlers to routes
Best Practices
- Keep routes modular and exportable; separate concerns by feature
- Use app.route to assemble API surfaces such as /api/users and /api/posts
- Prefer typed middleware via hono/factory to ensure type safety
- Validate inputs early with zod validators before processing
- Use factories to define external handlers and reuse them across routes
Example Use Cases
- Modular routes: users and posts live in separate files and are mounted with app.route('/api/users', users) and app.route('/api/posts', posts)
- Route groups with shared middleware: apply auth to all /api routes using api.use('*', authMiddleware) and keep public routes unprotected
- Chained routes for proper type inference: define RPC style routes in a single chain and export a type for AppType
- Basic handler: a simple handler fetches data and returns JSON to the client
- Factory based external handlers: define typed handlers with createFactory and reuse them across routes
Frequently Asked Questions
Add this skill to your agents