Skip to Content
guidesUsing Middleware

Last Updated: 3/9/2026


Using Middleware

Middleware in Hono processes requests before and after your handlers. Think of it like an onion: the request passes through layers of middleware, reaches your handler, then passes back through on the way out.

How Middleware Works

Middleware wraps your handler like layers of an onion. The request passes through each layer, reaches your handler, then passes back through on the way out.

import { Hono } from 'hono' import { logger } from 'hono/logger' import { cors } from 'hono/cors' const app = new Hono() // Layer 1: Logger (outer) app.use(logger()) // Layer 2: CORS (middle) app.use(cors()) // Center: Your handler app.get('/', (c) => c.text('Hello!'))

Request flow:

  1. Request arrives
  2. Logger middleware (start)
  3. CORS middleware (start)
  4. Handler executes
  5. CORS middleware (end)
  6. Logger middleware (end)
  7. Response sent

Built-in Middleware

Hono includes powerful middleware out of the box.

Logger

Log every request:

import { logger } from 'hono/logger' app.use(logger()) app.get('/', (c) => c.text('Hello'))
<-- GET / --> GET / 200 3ms

CORS

Enable Cross-Origin Resource Sharing:

import { cors } from 'hono/cors' // Simple CORS - allows all origins app.use(cors()) // Configured CORS app.use(cors({ origin: 'https://example.com', allowMethods: ['GET', 'POST', 'PUT', 'DELETE'], allowHeaders: ['Content-Type', 'Authorization'], maxAge: 600, credentials: true, }))

Basic Authentication

Protect routes with username/password:

import { basicAuth } from 'hono/basic-auth' app.use('/admin/*', basicAuth({ username: 'admin', password: 'secret', }) ) app.get('/admin/dashboard', (c) => { return c.text('Admin Dashboard') })

Bearer Authentication

Protect routes with bearer tokens:

import { bearerAuth } from 'hono/bearer-auth' app.use('/api/*', bearerAuth({ token: 'your-secret-token' }) )

With dynamic token verification:

app.use('/api/*', bearerAuth({ verifyToken: async (token, c) => { return token === await getValidToken() }, }) )

JWT Authentication

Validate JSON Web Tokens:

import { jwt } from 'hono/jwt' app.use('/api/*', jwt({ secret: 'my-secret-key', }) ) app.get('/api/protected', (c) => { const payload = c.get('jwtPayload') return c.json({ user: payload.sub }) })

With custom claims:

app.use('/api/*', jwt({ secret: 'my-secret-key', }) ) app.get('/api/me', (c) => { const payload = c.get('jwtPayload') return c.json({ userId: payload.sub, email: payload.email, role: payload.role, }) })

Compression

Compress responses:

import { compress } from 'hono/compress' app.use(compress()) // Or specify encoding app.use(compress({ encoding: 'gzip' }))

ETag

Generate ETags for caching:

import { etag } from 'hono/etag' app.use(etag()) app.get('/data', (c) => { return c.json({ data: 'This will have an ETag' }) })

Pretty JSON

Format JSON responses:

import { prettyJSON } from 'hono/pretty-json' app.use(prettyJSON()) app.get('/json', (c) => { return c.json({ message: 'This will be pretty-printed' }) })

Secure Headers

Add security headers:

import { secureHeaders } from 'hono/secure-headers' app.use(secureHeaders()) // Or customize app.use(secureHeaders({ contentSecurityPolicy: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], }, xFrameOptions: 'DENY', }))

Request ID

Add unique IDs to requests:

import { requestId } from 'hono/request-id' app.use(requestId()) app.get('/', (c) => { const id = c.get('requestId') return c.json({ requestId: id }) })

Timeout

Limit request execution time:

import { timeout } from 'hono/timeout' app.use('/api/*', timeout(5000)) // 5 seconds app.get('/api/slow', async (c) => { await longRunningTask() // Will timeout after 5s return c.json({ done: true }) })

Body Limit

Limit request body size:

import { bodyLimit } from 'hono/body-limit' app.use('/upload/*', bodyLimit({ maxSize: 50 * 1024, // 50kb onError: (c) => { return c.text('File too large', 413) }, }) )

Applying Middleware

Global Middleware

Apply to all routes:

import { logger } from 'hono/logger' app.use(logger()) // Applies to all routes app.get('/a', (c) => c.text('A')) // Logged app.get('/b', (c) => c.text('B')) // Logged

Path-Specific Middleware

Apply to specific paths:

import { basicAuth } from 'hono/basic-auth' // Only /admin/* routes app.use('/admin/*', basicAuth({ username: 'admin', password: 'secret' })) app.get('/admin/users', (c) => c.text('Users')) // Protected app.get('/public/info', (c) => c.text('Info')) // Not protected

Multiple Paths

Apply to multiple paths:

import { cors } from 'hono/cors' app.use(['/api/*', '/webhook/*'], cors()) app.get('/api/data', (c) => c.json({})) // CORS enabled app.get('/webhook/event', (c) => c.json({})) // CORS enabled app.get('/public', (c) => c.text('Public')) // No CORS

Per-Route Middleware

Apply to a single route:

import { cache } from 'hono/cache' app.get('/cached', cache({ cacheName: 'my-cache' }), (c) => c.json({ data: 'Cached response' }) ) app.get('/not-cached', (c) => c.json({ data: 'Not cached' }))

Multiple Middleware

Chain multiple middleware:

import { logger } from 'hono/logger' import { cors } from 'hono/cors' import { basicAuth } from 'hono/basic-auth' app.get('/protected', logger(), cors(), basicAuth({ username: 'admin', password: 'secret' }), (c) => c.text('Protected content') )

Custom Middleware

Basic Custom Middleware

app.use(async (c, next) => { console.log('Before handler') await next() // Call the next middleware/handler console.log('After handler') })

Timing Middleware

Measure request duration:

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

Error Handling Middleware

Catch and log errors:

app.use(async (c, next) => { try { await next() } catch (err) { console.error('Error:', err) return c.json({ error: 'Internal Server Error' }, 500) } })

Request Logging Middleware

Log request details:

app.use(async (c, next) => { console.log(`${c.req.method} ${c.req.url}`) console.log('Headers:', c.req.header()) await next() console.log('Status:', c.res.status) })

Authentication Middleware

Custom auth logic:

app.use('/api/*', async (c, next) => { const token = c.req.header('Authorization')?.replace('Bearer ', '') if (!token) { return c.json({ error: 'Unauthorized' }, 401) } const user = await verifyToken(token) if (!user) { return c.json({ error: 'Invalid token' }, 401) } c.set('user', user) await next() }) app.get('/api/me', (c) => { const user = c.get('user') return c.json({ user }) })

Creating Reusable Middleware

Factory Pattern

import { createMiddleware } from 'hono/factory' const timing = createMiddleware(async (c, next) => { const start = Date.now() await next() const ms = Date.now() - start c.header('X-Response-Time', `${ms}ms`) }) app.use(timing)

With Options

import { createMiddleware } from 'hono/factory' interface RateLimitOptions { max: number window: number } const rateLimit = (options: RateLimitOptions) => { const requests = new Map<string, number[]>() return createMiddleware(async (c, next) => { const ip = c.req.header('CF-Connecting-IP') || 'unknown' const now = Date.now() const userRequests = requests.get(ip) || [] const recentRequests = userRequests.filter( (time) => now - time < options.window ) if (recentRequests.length >= options.max) { return c.json({ error: 'Rate limit exceeded' }, 429) } recentRequests.push(now) requests.set(ip, recentRequests) await next() }) } // Use it app.use('/api/*', rateLimit({ max: 100, window: 60000 })) // 100 req/min

Type-Safe Middleware

import { createMiddleware } from 'hono/factory' type Env = { Variables: { user: { id: number; name: string } } } const auth = createMiddleware<Env>(async (c, next) => { const user = await authenticate(c.req.header('Authorization')) c.set('user', user) // Type-safe! await next() })

Middleware Execution Order

Order matters! Middleware executes in registration order:

import { logger } from 'hono/logger' import { cors } from 'hono/cors' import { basicAuth } from 'hono/basic-auth' // 1. Logger runs first app.use(logger()) // 2. Then CORS app.use(cors()) // 3. Then auth app.use('/admin/*', basicAuth({ username: 'admin', password: 'secret' })) // 4. Finally, the handler app.get('/admin/dashboard', (c) => c.text('Dashboard'))

Request flow:

Request → Logger (start) → CORS (start) → BasicAuth (start) → Handler ← BasicAuth (end) ← CORS (end) ← Logger (end) Response

Common pattern:

// 1. Logging/monitoring app.use(logger()) // 2. Security headers app.use(secureHeaders()) // 3. CORS app.use('/api/*', cors()) // 4. Authentication app.use('/api/*', jwt({ secret: 'secret' })) // 5. Request validation app.use('/api/*', bodyLimit({ maxSize: 1024 * 1024 })) // 6. Your routes app.get('/api/data', handler)

Third-Party Middleware

The community provides additional middleware:

GraphQL Server

import { graphqlServer } from '@hono/graphql-server' app.use('/graphql', graphqlServer({ schema: mySchema, }))

Firebase Auth

import { firebaseAuth } from '@hono/firebase-auth' app.use('/api/*', firebaseAuth({ projectId: 'your-project-id', }) )

Sentry

import { sentry } from '@hono/sentry' app.use(sentry({ dsn: 'your-sentry-dsn' }))

Common Patterns

Conditional Middleware

Apply middleware based on conditions:

app.use(async (c, next) => { if (c.req.header('X-Debug') === 'true') { console.log('Debug mode enabled') } await next() })

Middleware Composition

Combine multiple middleware:

import { logger } from 'hono/logger' import { cors } from 'hono/cors' import { compress } from 'hono/compress' const apiMiddleware = [ logger(), cors(), compress(), ] app.use('/api/*', ...apiMiddleware)

Early Returns

Stop execution in middleware:

app.use('/api/*', async (c, next) => { if (!c.req.header('X-API-Key')) { return c.json({ error: 'API key required' }, 401) // next() is NOT called - execution stops } await next() })

Response Modification

Modify responses after handlers:

app.use(async (c, next) => { await next() // Add header to all responses c.res.headers.set('X-Powered-By', 'Hono') // Modify JSON responses if (c.res.headers.get('Content-Type')?.includes('application/json')) { const body = await c.res.json() return c.json({ ...body, timestamp: new Date().toISOString(), }) } })

What’s Next?