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