Get the FREE Ultimate OpenClaw Setup Guide →

hono-patterns

Scanned
npx machina-cli add skill smicolon/ai-kit/hono-patterns --openclaw
Files (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

  1. Always type your app: new Hono<Env>()
  2. Validate all inputs with Zod
  3. Use createMiddleware for type-safe middleware
  4. Export AppType for RPC client
  5. Handle errors with HTTPException
  6. Use proper status codes: 200, 201, 204, 400, 401, 404, 500
  7. Organize routes in separate files
  8. 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

  1. Step 1: Split routes into modules (e.g., routes/users.ts exporting users) and import them where you mount the API
  2. Step 2: Use app.route to assemble the API surface and apply shared middleware with api.use or route stacking
  3. 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
Sponsor this space

Reach thousands of developers