Overview
Middleware are functions that execute during the request-response cycle. They have access to the Context object and can modify the request, response, or pass control to the next middleware.
What is Middleware?
Middleware functions are handlers that:
- Execute before the final route handler
- Can perform operations like authentication, logging, or validation
- Can modify the request or response
- Must call
next() to pass control to the next handler
- Can short-circuit the chain by returning a response
From src/types.ts:83-88:
type MiddlewareHandler<
E extends Env = any,
P extends string = string,
I extends Input = {},
R extends HandlerResponse<any> = Response,
> = (c: Context<E, P, I>, next: Next) => Promise<R | void>
Using Middleware
Global Middleware
Apply middleware to all routes:
import { Hono } from 'hono'
import { logger } from 'hono/logger'
const app = new Hono()
// Applies to all routes
app.use('*', logger())
app.get('/', (c) => c.text('Hello'))
app.get('/about', (c) => c.text('About'))
Path-Specific Middleware
Apply middleware to specific paths:
import { cors } from 'hono/cors'
const app = new Hono()
// Only applies to /api/* routes
app.use('/api/*', cors())
app.get('/api/users', (c) => c.json({ users: [] }))
app.get('/public', (c) => c.text('No CORS here'))
Inline Middleware
Use middleware for specific routes:
const authenticate = async (c, next) => {
const token = c.req.header('Authorization')
if (!token) {
return c.text('Unauthorized', 401)
}
await next()
}
app.get('/protected', authenticate, (c) => {
return c.text('Protected content')
})
The Next Function
The next function passes control to the next middleware or handler in the chain.
From src/types.ts:35:
type Next = () => Promise<void>
Calling Next
const myMiddleware = async (c, next) => {
console.log('Before handler')
await next() // Pass control to next handler
console.log('After handler')
}
app.use('*', myMiddleware)
app.get('/', (c) => {
console.log('Handler')
return c.text('Done')
})
// Output:
// Before handler
// Handler
// After handler
Always await the next() call. Without await, the middleware will continue executing before the handler completes, which can lead to unexpected behavior.
Multiple Calls to Next
Calling next() multiple times throws an error:
const badMiddleware = async (c, next) => {
await next()
await next() // Error: next() called multiple times
}
From src/compose.ts:33-35:
if (i <= index) {
throw new Error('next() called multiple times')
}
Middleware Execution Order
Middleware executes in the order it is defined, following the compose pattern from koa-compose.
From src/compose.ts:4-73:
export const compose = <E extends Env = Env>(
middleware: [[Function, unknown], unknown][] | [[Function]][],
onError?: ErrorHandler<E>,
onNotFound?: NotFoundHandler<E>
): ((context: Context, next?: Next) => Promise<Context>) => {
return (context, next) => {
let index = -1
return dispatch(0)
async function dispatch(i: number): Promise<Context> {
if (i <= index) {
throw new Error('next() called multiple times')
}
index = i
let res
let isError = false
let handler
if (middleware[i]) {
handler = middleware[i][0][0]
context.req.routeIndex = i
} else {
handler = (i === middleware.length && next) || undefined
}
if (handler) {
try {
res = await handler(context, () => dispatch(i + 1))
} catch (err) {
if (err instanceof Error && onError) {
context.error = err
res = await onError(err, context)
isError = true
} else {
throw err
}
}
} else {
if (context.finalized === false && onNotFound) {
res = await onNotFound(context)
}
}
if (res && (context.finalized === false || isError)) {
context.res = res
}
return context
}
}
}
Example: Execution Flow
app.use('*', async (c, next) => {
console.log('Middleware 1 - Before')
await next()
console.log('Middleware 1 - After')
})
app.use('*', async (c, next) => {
console.log('Middleware 2 - Before')
await next()
console.log('Middleware 2 - After')
})
app.get('/', (c) => {
console.log('Handler')
return c.text('Done')
})
// Output:
// Middleware 1 - Before
// Middleware 2 - Before
// Handler
// Middleware 2 - After
// Middleware 1 - After
Creating Custom Middleware
Basic Middleware
const requestLogger = async (c, next) => {
const start = Date.now()
await next()
const end = Date.now()
console.log(`${c.req.method} ${c.req.path} - ${end - start}ms`)
}
app.use('*', requestLogger)
Middleware with Configuration
function rateLimiter(options: { max: number; window: number }) {
const requests = new Map<string, number[]>()
return async (c, next) => {
const ip = c.req.header('x-forwarded-for') || 'unknown'
const now = Date.now()
const windowStart = now - options.window
const userRequests = requests.get(ip) || []
const recentRequests = userRequests.filter(time => time > windowStart)
if (recentRequests.length >= options.max) {
return c.text('Too Many Requests', 429)
}
recentRequests.push(now)
requests.set(ip, recentRequests)
await next()
}
}
app.use('*', rateLimiter({ max: 100, window: 60000 }))
Type-Safe Middleware
import { MiddlewareHandler } from 'hono'
type AuthEnv = {
Variables: {
user: { id: string; name: string }
}
}
const auth: MiddlewareHandler<AuthEnv> = async (c, next) => {
const token = c.req.header('Authorization')
if (!token) {
return c.text('Unauthorized', 401)
}
// Validate token and get user
const user = { id: '123', name: 'John' }
c.set('user', user)
await next()
}
app.use('*', auth)
app.get('/profile', (c) => {
const user = c.get('user') // Type-safe!
return c.json(user)
})
Built-in Middleware
Hono provides many built-in middleware for common tasks:
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { basicAuth } from 'hono/basic-auth'
import { bearerAuth } from 'hono/bearer-auth'
import { jwt } from 'hono/jwt'
import { compress } from 'hono/compress'
const app = new Hono()
app.use('*', logger())
app.use('*', cors())
app.use('/admin/*', basicAuth({ username: 'admin', password: 'secret' }))
app.use('/api/*', bearerAuth({ token: 'your-token' }))
Middleware Registration
From src/hono-base.ts:156-168:
this.use = (arg1: string | MiddlewareHandler<any>, ...handlers: MiddlewareHandler<any>[]) => {
if (typeof arg1 === 'string') {
this.#path = arg1
} else {
this.#path = '*'
handlers.unshift(arg1)
}
handlers.forEach((handler) => {
this.#addRoute(METHOD_NAME_ALL, this.#path, handler)
})
return this as any
}
The use method:
- Accepts a path (optional) and one or more handlers
- Defaults to
'*' if no path is provided
- Registers middleware for all HTTP methods
Short-Circuiting
Middleware can return a response to short-circuit the chain:
const requireAuth = async (c, next) => {
const token = c.req.header('Authorization')
if (!token) {
// Short-circuit: don't call next()
return c.text('Unauthorized', 401)
}
// Continue to next handler
await next()
}
app.use('/api/*', requireAuth)
Error Handling in Middleware
Errors thrown in middleware are caught and passed to the error handler:
const riskyMiddleware = async (c, next) => {
try {
await someAsyncOperation()
await next()
} catch (err) {
// Handle or re-throw
throw new Error('Middleware failed')
}
}
app.use('*', riskyMiddleware)
app.onError((err, c) => {
console.error(err)
return c.text('Internal Server Error', 500)
})
From src/compose.ts:50-60, errors are caught during dispatch:
try {
res = await handler(context, () => dispatch(i + 1))
} catch (err) {
if (err instanceof Error && onError) {
context.error = err
res = await onError(err, context)
isError = true
} else {
throw err
}
}
Modifying the Response
Middleware can modify the response after the handler executes:
const addHeaders = async (c, next) => {
await next()
c.header('X-Custom-Header', 'value')
c.header('X-Response-Time', Date.now().toString())
}
app.use('*', addHeaders)
Best Practices
- Always
await next() unless intentionally short-circuiting
- Register global middleware before route definitions
- Use specific paths for middleware when possible for better performance
- Keep middleware focused on a single responsibility
- Use TypeScript to ensure type safety for context variables
Avoid calling next() multiple times in the same middleware. This will throw an error and stop execution.
- Handlers - Learn about request handlers
- Context - Access request and response data
- Validation - Validate request data with middleware