Skip to main content
skill CX Codex

hono-middleware — Claude Code skill: Hono middleware patterns

A skill covering Hono middleware: writing your own, composing them, the built-in modules and the order they run in. Useful when a Hono app needs auth, CORS or logging.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add bobmatnyc/claude-mpm-skills --skill hono-middleware -a claude-code
Open on GitHub

What hono-middleware does

The skill walks through Hono's middleware system and its onion execution model: middleware runs before the handler on the way in, and after it on the way out. It covers inline middleware, path scoping with app.use('*', ...) and app.use('/api/*', ...), and reusable middleware built with createMiddleware from hono/factory.

A separate section deals with context variables. You declare types in new Hono<{ Variables: Variables }>(), write values with c.set('user', user) and read them in a handler with c.get('user'). That is how auth middleware hands the user down the chain while keeping types intact.

Built-in modules in the text include cors, bearerAuth, basicAuth, jwt, logger, prettyJSON, compress, etag and cache. For jwt and bearerAuth the secret or token goes straight into the config, so keep those in environment variables. One caveat: the cache example stops mid-line in the source, so there is no full description of caching here.

What it helps with

  • guard every /api/* route with JWT verification
  • set up CORS with an origin allowlist and credentials
  • pass the authenticated user from middleware into the handler with types
  • measure response time and expose it as a header
  • turn on gzip and ETag for static assets

Example prompts for the agent

  • show me how to attach jwt middleware to /api/* and read the payload in the handler
  • write a reusable logging middleware with createMiddleware
  • explain the execution order of three middleware and a handler in Hono

How to install hono-middleware

1Skills CLI (Vercel)

npx skills add bobmatnyc/claude-mpm-skills --skill hono-middleware -a claude-code

2Manually: put the files in place

~/.claude/skills/hono-middleware/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/hono-middleware/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about hono-middleware

How do I install hono-middleware in Claude?

Run npx skills add bobmatnyc/claude-mpm-skills --skill hono-middleware -a claude-code. The skill lands in ~/.claude/skills/hono-middleware/SKILL.md.

What does hono-middleware do?

It collects Hono middleware patterns: writing your own, composing them, the built-in modules and execution order. The model reaches for it when a project needs auth, CORS, logging or response compression.

What do I need for this skill to work?

A Hono project in TypeScript. For jwt and bearerAuth you need a secret or token — the examples pass it in the config, so keep it in environment variables.

Source text

SKILL.md · MIT
---
name: hono-middleware
description: Hono middleware patterns - creation, composition, built-in middleware, and execution order for web applications
user-invocable: false
disable-model-invocation: true
skill_version: 1.0.0
updated_at: 2025-01-03T00:00:00Z
tags: [hono, middleware, cors, authentication, logging, compression, security]
progressive_disclosure:
  entry_point:
    summary: "Middleware creation, composition, and 25+ built-in middleware for Hono applications"
    when_to_use: "Adding authentication, CORS, logging, compression, rate limiting, or custom request processing"
    quick_start: "1. Import middleware from hono/middleware 2. Apply with app.use() 3. Chain multiple middleware"
  references: []
context_limit: 800
---

# Hono Middleware Patterns

## Overview

Hono provides a powerful middleware system with an "onion" execution model. Middleware processes requests before handlers and responses after handlers, enabling cross-cutting concerns like authentication, logging, and CORS.

**Key Features**:
- Onion-style execution order
- Type-safe middleware creation with `createMiddleware`
- 25+ built-in middleware
- Context variable passing between middleware
- Async/await support throughout

## When to Use This Skill

Use Hono middleware when:
- Adding authentication/authorization
- Implementing CORS for cross-origin requests
- Adding request logging or timing
- Compressing responses
- Rate limiting API endpoints
- Validating requests before handlers

## Middleware Basics

### Inline Middleware

```typescript
import { Hono } from 'hono'

const app = new Hono()

// Simple logging middleware
app.use('*', async (c, next) => {
  console.log(`[${c.req.method}] ${c.req.url}`)
  await next()
})

// Path-specific middleware
app.use('/api/*', async (c, next) => {
  const start = Date.now()
  await next()
  const ms = Date.now() - start
  c.header('X-Response-Time', `${ms}ms`)
})
```

### Execution Order (Onion Model)

```typescript
app.use(async (c, next) => {
  console.log('1. Before (first in)')
  await next()
  console.log('6. After (first out)')
})

app.use(async (c, next) => {
  console.log('2. Before (second in)')
  await next()
  console.log('5. After (second out)')
})

app.use(async (c, next) => {
  console.log('3. Before (third in)')
  await next()
  console.log('4. After (third out)')
})

app.get('/', (c) => {
  console.log('Handler')
  return c.text('Hello!')
})

// Output:
// 1. Before (first in)
// 2. Before (second in)
// 3. Before (third in)
// Handler
// 4. After (third out)
// 5. After (second out)
// 6. After (first out)
```

### Creating Reusable Middleware

```typescript
import { createMiddleware } from 'hono/factory'

// Type-safe reusable middleware
const logger = createMiddleware(async (c, next) => {
  console.log(`[${new Date().toISOString()}] ${c.req.method} ${c.req.path}`)
  await next()
})

// Middleware with options
const timing = (headerName = 'X-Response-Time') => {
  return createMiddleware(async (c, next) => {
    const start = Date.now()
    await next()
    c.header(headerName, `${Date.now() - start}ms`)
  })
}

app.use(logger)
app.use(timing('X-Duration'))
```

## Context Variables

### Passing Data Between Middleware

```typescript
import { createMiddleware } from 'hono/factory'

// Define variable types
type Variables = {
  user: { id: string; email: string; role: string }
  requestId: string
}

const app = new Hono<{ Variables: Variables }>()

// Auth middleware sets user
const auth = createMiddleware<{ Variables: Variables }>(async (c, next) => {
  const token = c.req.header('Authorization')?.replace('Bearer ', '')

  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const user = await verifyToken(token)
  c.set('user', user)  // Type-safe!
  await next()
})

// Request ID middleware
const requestId = createMiddleware<{ Variables: Variables }>(async (c, next) => {
  c.set('requestId', crypto.randomUUID())
  await next()
})

app.use(requestId)
app.use('/api/*', auth)

app.get('/api/profile', (c) => {
  const user = c.get('user')      // Type: { id, email, role }
  const reqId = c.get('requestId') // Type: string
  return c.json({ user, requestId: reqId })
})
```

## Built-in Middleware

### CORS

```typescript
import { cors } from 'hono/cors'

// Simple - allow all origins
app.use('/api/*', cors())

// Configured
app.use('/api/*', cors({
  origin: ['https://example.com', 'https://app.example.com'],
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowHeaders: ['Content-Type', 'Authorization'],
  exposeHeaders: ['X-Total-Count'],
  credentials: true,
  maxAge: 86400
}))

// Dynamic origin
app.use('/api/*', cors({
  origin: (origin) => {
    return origin.endsWith('.example.com')
      ? origin
      : 'https://example.com'
  }
}))
```

### Bearer Auth

```typescript
import { bearerAuth } from 'hono/bearer-auth'

// Simple token validation
app.use('/api/*', bearerAuth({ token: 'my-secret-token' }))

// Multiple tokens
app.use('/api/*', bearerAuth({
  token: ['token1', 'token2', 'token3']
}))

// Custom verification
app.use('/api/*', bearerAuth({
  verifyToken: async (token, c) => {
    const user = await validateJWT(token)
    if (user) {
      c.set('user', user)
      return true
    }
    return false
  }
}))
```

### Basic Auth

```typescript
import { basicAuth } from 'hono/basic-auth'

app.use('/admin/*', basicAuth({
  username: 'admin',
  password: 'secret'  // pragma: allowlist secret
}))

// Multiple users
app.use('/admin/*', basicAuth({
  verifyUser: (username, password, c) => {
    return username === 'admin' && password === process.env.ADMIN_PASSWORD
  }
}))
```

### JWT Auth

```typescript
import { jwt } from 'hono/jwt'

app.use('/api/*', jwt({
  secret: 'my-jwt-secret'  // pragma: allowlist secret
}))

// Access payload in handler
app.get('/api/profile', (c) => {
  const payload = c.get('jwtPayload')
  return c.json({ userId: payload.sub })
})

// With algorithm
app.use('/api/*', jwt({
  secret: 'secret',  // pragma: allowlist secret
  alg: 'HS256'
}))
```

### Logger

```typescript
import { logger } from 'hono/logger'

// Default format
app.use(logger())

// Custom format
app.use(logger((str, ...rest) => {
  console.log(`[API] ${str}`, ...rest)
}))

// Output: <-- GET /api/users
//         --> GET /api/users 200 12ms
```

### Pretty JSON

```typescript
import { prettyJSON } from 'hono/pretty-json'

// Add ?pretty to format JSON responses
app.use(prettyJSON())

// GET /api/users         → {"users":[...]}
// GET /api/users?pretty  → formatted JSON
```

### Compress

```typescript
import { compress } from 'hono/compress'

app.use(compress())

// With options
app.use(compress({
  encoding: 'gzip'  // 'gzip' | 'deflate'
}))
```

### ETag

```typescript
import { etag } from 'hono/etag'

app.use(etag())

// Weak ETags
app.use(etag({ weak: true }))
```

### Cache

```typescript
import { cache } from 'hono/cache'

// Cloudflare Workers cache
app.use('/static/*', cache({
  cacheName: 'my-app',
  cacheControl: 'max-age=3600'
}))
```

### Secure Headers

```typescript
import { secureHeaders } from 'hono/secure-headers'

app.use(secureHeaders())

// Configured
app.use(secureHeaders({
  contentSecurityPolicy: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'unsafe-inline'"]
  },
  xFrameOptions: 'DENY',
  xXssProtection: '1; mode=block'
}))
```

### CSRF Protection

```typescript
import { csrf } from 'hono/csrf'

app.use(csrf())

// With options
app.use(csrf({
  origin: ['https://example.com']
}))
```

### Timeout

```typescript
import { timeout } from 'hono/timeout'

// 5 second timeout
app.use('/api/*', timeout(5000))

// Custom error
app.use('/api/*', timeout(5000, () => {
  return new Response('Request timeout', { status: 408 })
}))
```

### Request ID

```typescript
import { requestId } from 'hono/request-id'

app.use(requestId())

app.get('/', (c) => {
  const id = c.get('requestId')
  return c.json({ requestId: id })
})
```

## Advanced Patterns

### Conditional Middleware

```typescript
// Apply middleware based on condition
const conditionalAuth = createMiddleware(async (c, next) => {
  // Skip auth for health checks
  if (c.req.path === '/health') {
    return next()
  }

  // Apply auth for everything else
  const token = c.req.header('Authorization')
  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  await next()
})
```

### Middleware Composition

```typescript
import { every, some } from 'hono/combine'

// All middleware must pass
const strictAuth = every(
  bearerAuth({ token: 'secret' }),
  ipRestriction(['192.168.1.0/24']),
  rateLimiter({ max: 100 })
)

// Any middleware can pass
const flexibleAuth = some(
  bearerAuth({ token: 'api-key' }),
  basicAuth({ username: 'user', password: 'pass' })  // pragma: allowlist secret
)

app.use('/api/*', strictAuth)
app.use('/public/*', flexibleAuth)
```

### Modifying Responses

```typescript
const addHeaders = createMiddleware(async (c, next) => {
  await next()

  // Modify response after handler
  c.res.headers.set('X-Powered-By', 'Hono')
  c.res.headers.set('X-Request-Id', c.get('requestId'))
})

const transformResponse = createMiddleware(async (c, next) => {
  await next()

  // Replace response entirely
  const originalBody = await c.res.json()
  c.res = new Response(
    JSON.stringify({ data: originalBody, timestamp: Date.now() }),
    c.res
  )
})
```

### Error Handling in Middleware

```typescript
import { HTTPException } from 'hono/http-exception'

const safeMiddleware = createMiddleware(async (c, next) => {
  try {
    await next()
  } catch (error) {
    if (error instanceof HTTPException) {
      throw error  // Re-throw HTTP exceptions
    }

    // Log and convert other errors
    console.error('Middleware error:', error)
    throw new HTTPException(500, { message: 'Internal error' })
  }
})
```

### Rate Limiting

```typescript
// Simple in-memory rate limiter
const rateLimiter = (options: { max: number; window: number }) => {
  const requests = new Map<string, { count: number; reset: number }>()

  return createMiddleware(async (c, next) => {
    const ip = c.req.header('CF-Connecting-IP') || 'unknown'
    const now = Date.now()

    let record = requests.get(ip)

    if (!record || now > record.reset) {
      record = { count: 0, reset: now + options.window }
      requests.set(ip, record)
    }

    record.count++

    if (record.count > options.max) {
      c.header('Retry-After', String(Math.ceil((record.reset - now) / 1000)))
      return c.json({ error: 'Rate limit exceeded' }, 429)
    }

    c.header('X-RateLimit-Limit', String(options.max))
    c.header('X-RateLimit-Remaining', String(options.max - record.count))

    await next()
  })
}

app.use('/api/*', rateLimiter({ max: 100, window: 60000 }))
```

## Middleware Order Best Practices

```typescript
const app = new Hono()

// 1. Request ID (first - for tracking)
app.use(requestId())

// 2. Logger (early - to log all requests)
app.use(logger())

// 3. Security headers
app.use(secureHeaders())

// 4. CORS (before auth - for preflight)
app.use('/api/*', cors())

// 5. Compression
app.use(compress())

// 6. Rate limiting
app.use('/api/*', rateLimiter({ max: 100, window: 60000 }))

// 7. Authentication
app.use('/api/*', bearerAuth({ verifyToken }))

// 8. Request validation (after auth)
app.use('/api/*', validator)

// 9. Routes
app.route('/api', apiRoutes)

// 10. Not found handler (last)
app.notFound((c) => c.json({ error: 'Not found' }, 404))
```

## Quick Reference

### Built-in Middleware

| Middleware | Import | Purpose |
|------------|--------|---------|
| `cors` | `hono/cors` | Cross-origin requests |
| `bearerAuth` | `hono/bearer-auth` | Bearer token auth |
| `basicAuth` | `hono/basic-auth` | HTTP Basic auth |
| `jwt` | `hono/jwt` | JWT verification |
| `logger` | `hono/logger` | Request logging |
| `prettyJSON` | `hono/pretty-json` | JSON formatting |
| `compress` | `hono/compress` | Response compression |
| `etag` | `hono/etag` | ETag headers |
| `cache` | `hono/cache` | Response caching |
| `secureHeaders` | `hono/secure-headers` | Security headers |
| `csrf` | `hono/csrf` | CSRF protection |
| `timeout` | `hono/timeout` | Request timeout |
| `requestId` | `hono/request-id` | Request ID header |

### Third-Party Middleware

```bash
npm install @hono/zod-validator    # Zod validation
npm install @hono/graphql-server   # GraphQL
npm install @hono/swagger-ui       # Swagger UI
npm install @hono/prometheus       # Prometheus metrics
npm install @hono/sentry           # Sentry error tracking
```

## Related Skills

- **hono-core** - Framework fundamentals
- **hono-validation** - Request validation with Zod
- **hono-cloudflare** - Cloudflare-specific middleware

---

**Version**: Hono 4.x
**Last Updated**: January 2025
**License**: MIT

© bobmatnyc · MIT · bobmatnyc/claude-mpm-skills

Similar skills

All skills for Backend and APIs →

From the same set66

wordpress-advanced-architecture A skill for advanced WordPress work: custom REST routes, WP-CLI commands, transient and object caching. CX Codex +1 441installs digitalocean-management A skill for Claude, Codex and Cursor that walks through monitoring, uptime checks and Projects on DigitalOcean. Useful when you need alerts and operational visibility for droplets and clusters. CX Codex +1 438installs flask A skill that hands your agent ready Flask patterns: app factory, blueprints, request and response handling. Useful when you're building a REST API or microservice in Python. CX Codex +1 437installs hono-rpc A skill that walks through Hono RPC: export AppType on the server, call hc<AppType> on the client, skip codegen entirely. CX Codex +1 437installs golang-cli-cobra-viper A skill for building Go command-line tools: command structure with Cobra and configuration from flags, environment variables and files with Viper. CX Codex +1 425installs dependency-audit A skill that walks through dependency cleanup: outdated packages, security vulnerabilities and libraries nothing imports anymore. CX Codex +1 422installs wordpress-plugin-fundamentals A skill for building WordPress plugins on PHP 8.3+ with OOP, hooks, database access and the Settings API. Useful when the agent has to scaffold a plugin instead of guessing at structure. CX Codex +1 421installs digitalocean-compute Tells your agent which DigitalOcean compute to use for a job: Droplets, App Platform, Functions, DOKS or GPUs. Handy when you are deciding where an app or container should run. CX Codex +1 416installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor