Skip to content
Back to skills

Hono Patterns

ASecurity

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.

  • 6 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 28, 2026
ai-agentstypescriptapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned May 28, 2026

npx -y skills add smicolon/ai-kit --skill hono-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Hono Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Hono Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/smicolon-hono-patterns/badge)](https://www.skillsdirectory.com/skills/smicolon-hono-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
---
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()`:

```typescript
// 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

```typescript
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)

```typescript
// 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

```typescript
app.get('/users', async (c) => {
  const users = await fetchUsers()
  return c.json(users)
})
```

### Handler with Validation

```typescript
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:

```typescript
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

```typescript
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

```typescript
// 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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
// 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

```typescript
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

```typescript
// 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

```typescript
// 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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.