Agent Skills

hono-middleware

Hono middleware patterns - creation, composition, built-in middleware, and execution order for web applications

Install

npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill hono-middleware
SKILL.md

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

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)

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

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

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

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

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

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

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

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

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

import { compress } from 'hono/compress'

app.use(compress())

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

ETag

import { etag } from 'hono/etag'

app.use(etag())

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

Cache

import { cache } from 'hono/cache'

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

Secure Headers

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

import { csrf } from 'hono/csrf'

app.use(csrf())

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

Timeout

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

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

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

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

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

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

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

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

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

Related skills

entra-app-registrationmicrosoft606KGuides Microsoft Entra ID app registration, OAuth 2.0 authentication, and MSAL integration. USE FOR: create app registration, register Azure AD app, configure OAuth, set up authentication, add API permissions, generate service principal, MSAL example, console app auth, Entra ID setup, Azure AD authentication. DO NOT USE FOR: Key Vault secrets (use azure-keyvault-expiration-audit), general Azure resource security guidance.azure-messagingmicrosoft595KTroubleshoot and resolve issues with Azure Messaging SDKs for Event Hubs and Service Bus. Covers connection failures, authentication errors, message processing issues, and SDK configuration problems. WHEN: event hub SDK error, service bus SDK issue, messaging connection failure, AMQP error, event processor host issue, message lock lost, message lock expired, lock renewal, lock renewal batch, send timeout, receiver disconnected, SDK troubleshooting, azure messaging SDK, event hub consumer, servicentra-agent-idmicrosoft328KProvision Microsoft Entra Agent Identity Blueprints, BlueprintPrincipals, and per-instance Agent Identities via Microsoft Graph, and configure OAuth 2.0 token exchange (fmi_path, OBO, cross-tenant) including the Microsoft Entra SDK for AgentID sidecar. USE FOR: Agent Identity Blueprint, BlueprintPrincipal, agent OAuth, fmi_path token exchange, agent OBO, Workload Identity Federation for agents, polyglot agent auth, Microsoft.Identity.Web.AgentIdentities. DO NOT USE FOR: standard Entra app registsupabasesupabase298KUse when doing ANY task involving Supabase. Triggers: Supabase products (Database, Auth, Edge Functions, Realtime, Storage, Vectors, Cron, Queues); client libraries and SSR integrations (supabase-js, @supabase/ssr) in Next.js, React, SvelteKit, Astro, Remix; auth issues (login, logout, sessions, JWT, cookies, getSession, getUser, getClaims, RLS); Supabase CLI or MCP server; schema changes, migrations, declarative schemas, security audits, Postgres extensions (pg_graphql, pg_cron, pg_vector); deb

Search skills and MCP servers

Fuzzy search across 23,137 skills and servers