Skip to content

中间件

中间件在端点处理器(Handler)之前/之后工作。我们可以在 dispatch 之前获取 Request,或在 dispatch 之后操作 Response

中间件的定义

  • 处理器(Handler)- 应该返回 Response 对象。只会调用一个处理器。
  • 中间件(Middleware)- 应该 await next() 且不返回任何内容以调用下一个中间件, 返回一个 Response 以提前退出。

用户可以使用 app.use 注册中间件,也可以使用 app.HTTP_METHOD 以及处理器。对于这个功能,很容易指定路径和方法。

ts
// 匹配任何方法,所有路由
app.use(logger())

// 指定路径
app.use('/posts/*', cors())

// 指定方法和路径
app.post('/posts/*', basicAuth())

如果处理器返回 Response,它将被用于最终用户并停止处理。

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

在这种情况下,四个中间件在 dispatch 之前按如下方式处理:

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

执行顺序

中间件的执行顺序取决于其注册的顺序。 第一个注册的中间件的 next 之前的过程最先执行, 而 next 之后的过程最后执行。 见下文。

ts
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!')
})

结果如下。

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

请注意,如果处理器或任何中间件抛出错误,hono 将捕获它,要么将其传递给 你的 app.onError() 回调,要么在将其返回到中间件链之前自动将其转换为 500 响应。这意味着 next() 永远不会抛出,因此无需将其包裹在 try/catch/finally 中。

内置中间件

Hono 拥有内置中间件。

ts
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

在 Deno 中,可以使用与 Hono 版本不同的中间件版本,但这可能导致错误。 例如,此代码无法工作,因为版本不同。

ts
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(() => ({
    // ...
  }))
)

自定义中间件

你可以直接在 app.use() 内部编写自己的中间件:

ts
// 自定义日志
app.use(async (c, next) => {
  console.log(`[${c.req.method}] ${c.req.url}`)
  await next()
})

// 添加自定义头
app.use('/message/*', async (c, next) => {
  await next()
  c.header('x-message', 'This is middleware!')
})

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

但是,直接将中间件嵌入 app.use() 内可能会限制其可重用性。因此,我们可以将中间件分离到不同的文件中。

为了确保我们在分离中间件时不会丢失 contextnext 的类型定义,我们可以使用 Hono 工厂中的 createMiddleware()。这也允许我们类型安全地 访问我们在 Contextset 的数据

ts
import { createMiddleware } from 'hono/factory'

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

INFO

类型泛型可以与 createMiddleware 一起使用:

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

在 Next 之后修改响应

此外,中间件可以设计为在必要时修改响应:

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

在中间件参数内部访问 Context

要在中间件参数内部访问 context,直接使用 app.use 提供的 context 参数。请参阅下面的示例以说明。

ts
import { cors } from 'hono/cors'

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

在中间件中扩展 Context

要在中间件内部扩展 context,使用 c.set。你可以通过向 createMiddleware 函数传递 { Variables: { yourVariable: YourVariableType } } 泛型参数来实现类型安全。

ts
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!'))
})

跨链式中间件的类型推断

当你使用 .use() 链式调用多个中间件时,Hono 会自动累积 Variables 类型。跟随中间件链的路由处理器可以类型安全地访问所有前面中间件中的变量:

ts
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) => {
    // `user` 和 `db` 都可用且类型安全
    const user = c.var.user // { id: string; name: string }
    const db = c.var.db // { query: (sql: string) => Promise<unknown> }
    return c.json({ user })
  })

之所以有效,是因为每个 .use() 调用都会返回一个具有合并类型的新 Hono 实例,因此类型会随着中间件的链式调用而增长。这消除了大多数用例中需要手动预先声明组合 Env 类型的需求。

第三方中间件

内置中间件不依赖外部模块,但第三方中间件可以依赖第三方库。因此使用它们,我们可以构建更复杂的应用程序。

我们可以探索各种 第三方中间件。 例如,我们有 GraphQL Server 中间件、Sentry 中间件、Firebase Auth 中间件等。