> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/honojs/hono/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Middleware

> Learn how to create your own middleware in Hono

Custom middleware allows you to extend Hono with your own functionality. This guide shows you how to create middleware for various use cases.

## Basic Middleware Structure

A middleware is a function that receives the context object and a `next()` function:

```typescript theme={null}
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono'

const app = new Hono()

const myMiddleware: MiddlewareHandler = async (c, next) => {
  // Code executed before the route handler
  await next()
  // Code executed after the route handler
}

app.use(myMiddleware)
```

## The Context Object

The context object (`c`) provides access to the request and response:

```typescript theme={null}
import type { MiddlewareHandler } from 'hono'

const exampleMiddleware: MiddlewareHandler = async (c, next) => {
  // Request properties
  const method = c.req.method
  const url = c.req.url
  const path = c.req.path
  const query = c.req.query('key')
  const header = c.req.header('Authorization')
  
  // Store values in context
  c.set('startTime', Date.now())
  
  await next()
  
  // Access stored values
  const startTime = c.get('startTime')
  
  // Modify response
  c.res.headers.set('X-Custom-Header', 'value')
}
```

## Calling next()

The `next()` function passes control to the next middleware or route handler. Always `await` it:

```typescript theme={null}
import type { MiddlewareHandler } from 'hono'

const middleware: MiddlewareHandler = async (c, next) => {
  // ✅ Correct: await next()
  await next()
  
  // ❌ Wrong: not awaiting
  // next() // Don't do this!
}
```

<Warning>
  Always `await next()` to ensure proper middleware execution order and error handling.
</Warning>

## Common Middleware Patterns

### Before/After Pattern

Execute code before and after the route handler:

```typescript theme={null}
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono'

const app = new Hono()

const timingMiddleware: MiddlewareHandler = async (c, next) => {
  const start = Date.now()
  
  await next()
  
  const duration = Date.now() - start
  c.res.headers.set('X-Response-Time', `${duration}ms`)
}

app.use(timingMiddleware)
app.get('/', (c) => c.text('Hello!'))
```

### Early Return Pattern

Return a response without calling `next()`:

```typescript theme={null}
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono'

const app = new Hono()

const authMiddleware: MiddlewareHandler = async (c, next) => {
  const token = c.req.header('Authorization')
  
  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  
  // Token exists, continue to next handler
  await next()
}

app.use('/admin/*', authMiddleware)
app.get('/admin/dashboard', (c) => c.text('Dashboard'))
```

### Modifying Request Data

Add data to the context for later use:

```typescript theme={null}
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono'

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

const userMiddleware: MiddlewareHandler = async (c, next) => {
  const userId = c.req.header('X-User-Id') || 'anonymous'
  const role = c.req.header('X-User-Role') || 'guest'
  
  c.set('userId', userId)
  c.set('role', role)
  
  await next()
}

app.use(userMiddleware)
app.get('/', (c) => {
  const userId = c.get('userId')
  const role = c.get('role')
  return c.json({ userId, role })
})
```

### Modifying Response

Change the response after the handler:

```typescript theme={null}
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono'

const app = new Hono()

const jsonWrapperMiddleware: MiddlewareHandler = async (c, next) => {
  await next()
  
  // Wrap all JSON responses in a standard format
  if (c.res.headers.get('Content-Type')?.includes('application/json')) {
    const originalBody = await c.res.json()
    c.res = new Response(
      JSON.stringify({
        success: true,
        data: originalBody,
        timestamp: new Date().toISOString()
      }),
      { headers: c.res.headers }
    )
  }
}

app.use('/api/*', jsonWrapperMiddleware)
app.get('/api/users', (c) => c.json([{ id: 1, name: 'Alice' }]))
```

## Parameterized Middleware

Create middleware that accepts options:

```typescript theme={null}
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono'

interface RateLimitOptions {
  max: number
  window: number
}

const rateLimit = (options: RateLimitOptions): MiddlewareHandler => {
  const requests = new Map<string, number[]>()
  
  return async (c, next) => {
    const ip = c.req.header('CF-Connecting-IP') || 'unknown'
    const now = Date.now()
    const windowStart = now - options.window
    
    // Get existing requests for this IP
    const ipRequests = requests.get(ip) || []
    
    // Filter out old requests
    const recentRequests = ipRequests.filter(time => time > windowStart)
    
    if (recentRequests.length >= options.max) {
      return c.json(
        { error: 'Rate limit exceeded' },
        429
      )
    }
    
    // Add current request
    recentRequests.push(now)
    requests.set(ip, recentRequests)
    
    await next()
  }
}

const app = new Hono()

app.use('/api/*', rateLimit({ max: 10, window: 60000 })) // 10 requests per minute
app.get('/api/data', (c) => c.json({ data: 'value' }))
```

## Error Handling

Handle errors in middleware:

```typescript theme={null}
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono'
import { HTTPException } from 'hono/http-exception'

const app = new Hono()

const errorHandlerMiddleware: MiddlewareHandler = async (c, next) => {
  try {
    await next()
  } catch (error) {
    if (error instanceof HTTPException) {
      // Handle HTTP exceptions
      return c.json(
        { error: error.message },
        error.status
      )
    }
    
    // Handle other errors
    console.error('Unhandled error:', error)
    return c.json(
      { error: 'Internal Server Error' },
      500
    )
  }
}

app.use(errorHandlerMiddleware)
app.get('/error', (c) => {
  throw new HTTPException(400, { message: 'Bad Request' })
})
```

## Async Operations

Perform async operations in middleware:

```typescript theme={null}
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono'

const app = new Hono<{
  Variables: {
    user: { id: string; email: string } | null
  }
}>()

const loadUserMiddleware: MiddlewareHandler = async (c, next) => {
  const userId = c.req.header('X-User-Id')
  
  if (userId) {
    // Simulate async database call
    const user = await fetchUserFromDatabase(userId)
    c.set('user', user)
  } else {
    c.set('user', null)
  }
  
  await next()
}

async function fetchUserFromDatabase(userId: string) {
  // Simulated async database call
  return { id: userId, email: `user${userId}@example.com` }
}

app.use(loadUserMiddleware)
app.get('/', (c) => {
  const user = c.get('user')
  return c.json({ user })
})
```

## TypeScript Types

Properly type your middleware for better type safety:

```typescript theme={null}
import { Hono } from 'hono'
import type { Context, MiddlewareHandler } from 'hono'

// Define your environment type
type Env = {
  Variables: {
    requestId: string
    startTime: number
  }
}

// Typed middleware
const requestIdMiddleware: MiddlewareHandler<Env> = async (c, next) => {
  c.set('requestId', crypto.randomUUID())
  c.set('startTime', Date.now())
  await next()
}

// Or use the Context type directly
const loggingMiddleware = async (c: Context<Env>, next: () => Promise<void>) => {
  const requestId = c.get('requestId')
  const startTime = c.get('startTime')
  
  console.log(`[${requestId}] Request started`)
  await next()
  console.log(`[${requestId}] Request completed in ${Date.now() - startTime}ms`)
}

const app = new Hono<Env>()

app.use(requestIdMiddleware)
app.use(loggingMiddleware)
app.get('/', (c) => {
  const requestId = c.get('requestId') // TypeScript knows this is a string
  return c.json({ requestId })
})
```

## Factory Pattern

Create a reusable middleware factory:

```typescript theme={null}
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono'

interface HeaderOptions {
  name: string
  value: string | ((c: Context) => string)
}

function addHeader(options: HeaderOptions): MiddlewareHandler {
  return async (c, next) => {
    await next()
    
    const value = typeof options.value === 'function'
      ? options.value(c)
      : options.value
    
    c.res.headers.set(options.name, value)
  }
}

const app = new Hono()

// Use the factory to create middleware
app.use(addHeader({ name: 'X-Powered-By', value: 'Hono' }))
app.use(addHeader({
  name: 'X-Request-Path',
  value: (c) => c.req.path
}))

app.get('/', (c) => c.text('Hello!'))
```

## Testing Middleware

Test your custom middleware:

```typescript theme={null}
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono'
import { describe, it, expect } from 'vitest'

const authMiddleware: MiddlewareHandler = async (c, next) => {
  const token = c.req.header('Authorization')
  if (!token || token !== 'Bearer valid-token') {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  await next()
}

describe('authMiddleware', () => {
  it('should allow valid token', async () => {
    const app = new Hono()
    app.use(authMiddleware)
    app.get('/test', (c) => c.json({ success: true }))
    
    const res = await app.request('/test', {
      headers: { Authorization: 'Bearer valid-token' }
    })
    
    expect(res.status).toBe(200)
    expect(await res.json()).toEqual({ success: true })
  })
  
  it('should reject invalid token', async () => {
    const app = new Hono()
    app.use(authMiddleware)
    app.get('/test', (c) => c.json({ success: true }))
    
    const res = await app.request('/test', {
      headers: { Authorization: 'Bearer invalid-token' }
    })
    
    expect(res.status).toBe(401)
    expect(await res.json()).toEqual({ error: 'Unauthorized' })
  })
})
```

## Best Practices

<AccordionGroup>
  <Accordion title="Always await next()">
    Failing to await `next()` can cause unexpected behavior and broken error handling:

    ```typescript theme={null}
    // ❌ Wrong
    const bad: MiddlewareHandler = async (c, next) => {
      console.log('before')
      next() // Missing await!
      console.log('after')
    }

    // ✅ Correct
    const good: MiddlewareHandler = async (c, next) => {
      console.log('before')
      await next()
      console.log('after')
    }
    ```
  </Accordion>

  <Accordion title="Don't modify the context object directly">
    Use `c.set()` to store values instead of adding properties:

    ```typescript theme={null}
    // ❌ Wrong
    const bad: MiddlewareHandler = async (c, next) => {
      (c as any).userId = '123' // Don't do this
      await next()
    }

    // ✅ Correct
    const good: MiddlewareHandler = async (c, next) => {
      c.set('userId', '123')
      await next()
    }
    ```
  </Accordion>

  <Accordion title="Handle errors properly">
    Wrap risky operations in try-catch blocks:

    ```typescript theme={null}
    const middleware: MiddlewareHandler = async (c, next) => {
      try {
        const data = await riskyOperation()
        c.set('data', data)
        await next()
      } catch (error) {
        console.error('Error:', error)
        return c.json({ error: 'Operation failed' }, 500)
      }
    }
    ```
  </Accordion>

  <Accordion title="Use TypeScript for better type safety">
    Define your environment types for better IDE support:

    ```typescript theme={null}
    type Env = {
      Variables: {
        userId: string
        role: 'admin' | 'user'
      }
    }

    const middleware: MiddlewareHandler<Env> = async (c, next) => {
      c.set('userId', '123')
      c.set('role', 'admin') // TypeScript ensures only valid roles
      await next()
    }
    ```
  </Accordion>

  <Accordion title="Keep middleware focused">
    Each middleware should have a single, clear responsibility:

    ```typescript theme={null}
    // ✅ Good: Single responsibility
    const loggingMiddleware: MiddlewareHandler = async (c, next) => {
      console.log(`${c.req.method} ${c.req.url}`)
      await next()
    }

    // ❌ Bad: Too many responsibilities
    const kitchenSinkMiddleware: MiddlewareHandler = async (c, next) => {
      console.log('logging')
      // validate auth
      // check rate limits
      // transform request
      // etc...
      await next()
    }
    ```
  </Accordion>
</AccordionGroup>

## Related Topics

<CardGroup cols={2}>
  <Card title="Built-in Middleware" icon="box" href="/middleware/built-in">
    Explore built-in middleware for reference
  </Card>

  <Card title="Third-Party Middleware" icon="puzzle-piece" href="/middleware/third-party">
    Learn about external middleware packages
  </Card>
</CardGroup>
