Skip to main content

Middleware

Model-written

contextmethodsetupresultanalysislimitation

Middleware

Middleware wraps endpoint handlers before and after dispatch

Middleware works before/after the endpoint Handler. We can get the Request before dispatching or manipulate the Response after dispatching.Middleware works before/after the endpoint Handler. We can get the Request before dispatching or manipulate the Response after dispatching.Middleware works before and after the endpoint Handler It can access the Request before dispatch and manipulate the Response afterwardMiddleware works before and after the endpoint Handlerendpoint Handler

Definition of Middleware

Handlers return Response; middleware awaits next or early-exits

  • Handler - should return Response object. Only one handler will be called.
    Handler - should return Response object. Only one handler will be called.
    A Handler returns a Response and only one is called
    A Handler returns a Response and only one is called
    Handler
  • Middleware - should await next() and return nothing to call the next Middleware, or return a Response to early-exit.
    Middleware - should await next() and return nothing to call the next Middleware, or return a Response to early-exit.
    Middleware awaits next and returns nothing, or returns a Response Awaiting next calls the next middleware; returning a Response exits early
    Middleware awaits next and returns nothing, or returns a Response
    Middleware

The user can register middleware using app.use or using app.HTTP_METHOD as well as the handlers. For this feature, it's easy to specify the path and the method.The user can register middleware using app.use or using app.HTTP_METHOD as well as the handlers. For this feature, it's easy to specify the path and the method.Register middleware with app.use or app.HTTP_METHOD Both let you specify the path and method easilyRegister middleware with app.use or app.HTTP_METHODapp.use

// match any method, all routes
app.use(logger())

// specify path
app.use('/posts/*', cors())

// specify method and path
app.post('/posts/*', basicAuth())

If the handler returns Response, it will be used for the end-user and will stop processing.If the handler returns Response, it will be used for the end-user and will stop processing.A handler returning Response stops processing for the end-userA handler returning Response stops processing for the end-userend-user Response

app.post('/posts', (c) => c.text('Created!', 201))

In this case, four middleware are processed before dispatching like this:In this case, four middleware are processed before dispatching like this:Four middleware are processed before the handler dispatchesFour middleware are processed before the handler dispatchesmiddleware processed

logger() -> cors() -> basicAuth() -> *handler*

Execution order

Execution order follows registration order, nesting around next

The order in which Middleware is executed is determined by the order in which it is registered. The process before the next of the first registered Middleware is executed first, and the process after the next is executed last. See below.The order in which Middleware is executed is determined by the order in which it is registered. The process before the next of the first registered Middleware is executed first, and the process after the next is executed last.Middleware executes in registration order around next Code before next of the first registered runs first and after next runs lastMiddleware executes in registration order around nextregistration order

app.use(async (_, next) => {
  console.log('middleware 1 start')
  await next()
  console.log('middleware 1 end')
})
app.use(async (_, next) => {
  console.log('middleware 2 start')
  await next()
  console.log('middleware 2 end')
})
app.use(async (_, next) => {
  console.log('middleware 3 start')
  await next()
  console.log('middleware 3 end')
})

app.get('/', (c) => {
  console.log('handler')
  return c.text('Hello!')
})

Result is the following.Result is the following.Nested logs show middleware 1 start, 2, 3, handler, then reverseNested logs show middleware 1 start, 2, 3, handler, then reverseNested logs

middleware 1 start
  middleware 2 start
    middleware 3 start
      handler
    middleware 3 end
  middleware 2 end
middleware 1 end

Note that if the handler or any middleware throws, hono will catch it and either pass it to your app.onError() callback or automatically convert it to a 500 response before returning it up the chain of middleware. This means that next() will never throw, so there is no need to wrap it in a try/catch/finally.Note that if the handler or any middleware throws, hono will catch it and either pass it to your app.onError() callback or automatically convert it to a 500 response before returning it up the chain of middleware. This means that next() will never throw, so there is no need to wrap it in a try/catch/finally.next() never throws, so no try/catch needed around it Hono catches thrown errors, passing them to app.onError or converting to a 500next() never throws, so no try/catch needed around itnext()

Built-in Middleware

Hono ships built-in middleware like poweredBy, logger, basicAuth

Hono has built-in middleware.Hono has built-in middleware.Hono has built-in middleware like poweredBy, logger, basicAuthHono has built-in middleware like poweredBy, logger, basicAuthbuilt-in middleware

import { Hono } from 'hono'
import { poweredBy } from 'hono/powered-by'
import { logger } from 'hono/logger'
import { basicAuth } from 'hono/basic-auth'

const app = new Hono()

app.use(poweredBy())
app.use(logger())

app.use(
  '/auth/*',
  basicAuth({
    username: 'hono',
    password: 'acoolproject',
  })
)

Warning

In Deno, it is possible to use a different version of middleware than the Hono version, but this can lead to bugs. For example, this code is not working because the version is different.In Deno, it is possible to use a different version of middleware than the Hono version, but this can lead to bugs. For example, this code is not working because the version is different.Mismatched Deno middleware versions can cause bugs Using a different middleware version than Hono's can break code such as upgradeWebSocketMismatched Deno middleware versions can cause bugsDeno versions

import { Hono } from 'jsr:@hono/hono@4.4.0'
import { upgradeWebSocket } from 'jsr:@hono/hono@4.4.5/deno'

const app = new Hono()

app.get(
  '/ws',
  upgradeWebSocket(() => ({
    // ...
  }))
)

Custom Middleware

Write custom middleware inline or separate via createMiddleware

You can write your own middleware directly inside app.use():You can write your own middleware directly inside app.use():Write custom middleware directly inside app.use()Write custom middleware directly inside app.use()Custom middleware

// Custom logger
app.use(async (c, next) => {
  console.log(`[${c.req.method}] ${c.req.url}`)
  await next()
})

// Add a custom header
app.use('/message/*', async (c, next) => {
  await next()
  c.header('x-message', 'This is middleware!')
})

app.get('/message/hello', (c) => c.text('Hello Middleware!'))

However, embedding middleware directly within app.use() can limit its reusability. Therefore, we can separate our middleware into different files.However, embedding middleware directly within app.use() can limit its reusability. Therefore, we can separate our middleware into different files.Inline middleware limits reusability, so separate into files Moving middleware into its own file makes it reusable across the appInline middleware limits reusability, so separate into filesInline middleware

To ensure we don't lose type definitions for context and next, when separating middleware, we can use createMiddleware() from Hono's factory. This also allows us to type-safely access data we've set in Context from downstream handlers.To ensure we don't lose type definitions for context and next, when separating middleware, we can use createMiddleware() from Hono's factory. This also allows us to type-safely access data we've set in Context from downstream handlers.createMiddleware() preserves context and next types when separating It also enables type-safe access to data set in Context from downstream handlerscreateMiddleware() preserves context and next types when separatingcreateMiddleware()

import { createMiddleware } from 'hono/factory'

const logger = createMiddleware(async (c, next) => {
  console.log(`[${c.req.method}] ${c.req.url}`)
  await next()
})

Note

Type generics can be used with createMiddleware:Type generics can be used with createMiddleware:createMiddleware accepts type generics such as BindingscreateMiddleware accepts type generics such as BindingscreateMiddleware

createMiddleware<{Bindings: Bindings}>(async (c, next) =>

Modify the Response After Next

Middleware can replace the response after awaiting next

Additionally, middleware can be designed to modify responses if necessary:Additionally, middleware can be designed to modify responses if necessary:Middleware can modify responses, replacing c.res after nextMiddleware can modify responses, replacing c.res after nextc.res

const stripRes = createMiddleware(async (c, next) => {
  await next()
  c.res = undefined
  c.res = new Response('New Response')
})

Context access inside Middleware arguments

Use the context parameter inside middleware arguments

To access the context inside middleware arguments, directly use the context parameter provided by app.use. See the example below for clarification.To access the context inside middleware arguments, directly use the context parameter provided by app.use. See the example below for clarification.Use the context parameter inside middleware arguments Pass the middleware the same c and next to access context while configuring itUse the context parameter inside middleware argumentscontext parameter

import { cors } from 'hono/cors'

app.use('*', async (c, next) => {
  const middleware = cors({
    origin: c.env.CORS_ORIGIN,
  })
  return middleware(c, next)
})

Extending the Context in Middleware

Extend context type-safely with c.set and Variables generic

To extend the context inside middleware, use c.set. You can make this type-safe by passing a { Variables: { yourVariable: YourVariableType } } generic argument to the createMiddleware function.To extend the context inside middleware, use c.set. You can make this type-safe by passing a { Variables: { yourVariable: YourVariableType } } generic argument to the createMiddleware function.Extend context with c.set and a Variables generic for type safety Passing Variables to createMiddleware lets downstream handlers use the value type-safelyExtend context with c.set and a Variables generic for type safetyc.set

import { createMiddleware } from 'hono/factory'

const echoMiddleware = createMiddleware<{
  Variables: {
    echo: (str: string) => string
  }
}>(async (c, next) => {
  c.set('echo', (str) => str)
  await next()
})

app.get('/echo', echoMiddleware, (c) => {
  return c.text(c.var.echo('Hello!'))
})

Type Inference Across Chained Middleware

Chained .use() calls accumulate Variables types automatically

When you chain multiple middleware using .use(), Hono automatically accumulates the Variables types. Route handlers that follow the middleware chain can access all variables from every preceding middleware in a type-safe way:When you chain multiple middleware using .use(), Hono automatically accumulates the Variables types. Route handlers that follow the middleware chain can access all variables from every preceding middleware in a type-safe way:Chained .use() calls accumulate Variables types automatically Route handlers after the chain access all preceding variables type-safelyChained .use() calls accumulate Variables types automaticallyVariables types

import { createMiddleware } from 'hono/factory'

const authMiddleware = createMiddleware<{
  Variables: { user: { id: string; name: string } }
}>(async (c, next) => {
  c.set('user', { id: '123', name: 'Alice' })
  await next()
})

const dbMiddleware = createMiddleware<{
  Variables: { db: { query: (sql: string) => Promise<unknown> } }
}>(async (c, next) => {
  c.set('db', {
    query: async (sql) => {
      /* ... */
    },
  })
  await next()
})

const app = new Hono()
  .use(authMiddleware)
  .use(dbMiddleware)
  .get('/', (c) => {
    // Both `user` and `db` are available and type-safe
    const user = c.var.user // { id: string; name: string }
    const db = c.var.db // { query: (sql: string) => Promise<unknown> }
    return c.json({ user })
  })

This works because each .use() call returns a new Hono instance with the merged type, so the type grows as middleware is chained. This eliminates the need to manually declare a combined Env type upfront for most use cases.This works because each .use() call returns a new Hono instance with the merged type, so the type grows as middleware is chained. This eliminates the need to manually declare a combined Env type upfront for most use cases..use() returns a new Hono instance with merged types The type grows as middleware chains, eliminating manual combined Env declarations.use() returns a new Hono instance with merged typesHono instance

Third-party Middleware

Third-party middleware depends on libraries for complex apps

Built-in middleware does not depend on external modules, but third-party middleware can depend on third-party libraries. So with them, we may make a more complex application.Built-in middleware does not depend on external modules, but third-party middleware can depend on third-party libraries. So with them, we may make a more complex application.Third-party middleware depends on external libraries for complex appsThird-party middleware depends on external libraries for complex appsThird-party middleware

We can explore a variety of third-party middleware. For example, we have GraphQL Server Middleware, Sentry Middleware, Firebase Auth Middleware, and others.We can explore a variety of third-party middleware. For example, we have GraphQL Server Middleware, Sentry Middleware, Firebase Auth Middleware, and others.Third-party middleware includes GraphQL, Sentry, and Firebase AuthThird-party middleware includes GraphQL, Sentry, and Firebase AuthThird-party middleware