Skip to content

Express 适配器

@inferdi/express 是 Express 5 中间件。它创建一个请求作用域,将其暴露为 req.di,并在 Node 响应 finish 或 close 之后释放它。

安装

bash
pnpm add @inferdi/inferdi @inferdi/express express
pnpm add -D @types/express
ts
import express from 'express'
import { Container } from '@inferdi/inferdi'
import { inferdiExpress } from '@inferdi/express'

请求作用域

ts
type RequestContext = {
  requestId: string
  userId?: string
  ip?: string
}

class Users {
  constructor(readonly request: RequestContext) {}

  profile(id: string) {
    return { id, userId: this.request.userId }
  }
}

const root = new Container()
  .declareScopeInputs<{ request: RequestContext }>()
  .registerClass('users', Users, ['request'], 'scoped')
const openRequestScope = (request: RequestContext) =>
  root.createScope({ request })
type RequestScope = ReturnType<typeof openRequestScope>

declare global {
  namespace Express {
    interface Request {
      di: RequestScope
    }
  }
}

const app = express()

app.use(inferdiExpress({
  container: root,
  createScope: (_root, req) => openRequestScope({
    requestId: crypto.randomUUID(),
    userId: req.get('x-user-id') || undefined,
    ip: req.ip
  })
}))

app.get('/users/:id', async (req, res, next) => {
  try {
    res.json(await req.di.get('users').profile(req.params.id))
  } catch (error) {
    next(error)
  }
})

该适配器不会用 anyunknown 或某个基类容器全局增强 Express.Request。具体的请求类型由应用自己拥有。

选项

选项默认值描述
container必填根容器。该中间件从不释放它。
createScoperoot.createScope()自定义请求作用域创建。
setupScope在作用域创建后执行额外初始化。
disposeScopescope.dispose()自定义释放。
autoDisposetruefalse 或返回 false 的谓词会转移所有权。
onDisposeErrorconsole.error清理失败的接收端。

流式与后台工作

普通的 Express 流式响应不需要跳过,因为适配器会等待 finishclose

当工作有意比 HTTP 响应存活更久时,使用 skipInferdiDispose(req)

ts
import { skipInferdiDispose } from '@inferdi/express'

app.get('/background', (req, res) => {
  skipInferdiDispose(req)
  const scope = req.di

  queue.add(async () => {
    try {
      await scope.get('jobs').run()
    } finally {
      await scope.dispose()
    }
  })

  res.status(202).json({ status: 'queued' })
})

失败请求的注意事项

与其他适配器不同,Express 无法在一个已被处理的路由错误上可靠地强制释放被跳过的作用域。Express 中间件是回调式的;在 next() 返回之后,适配器无法观察到一个之后被错误处理器处理掉的下游异常。如果某个路由调用了 skipInferdiDispose(req) 然后失败,该作用域仍归应用所有。请在你自己的错误路径中释放它,或者避免将跳过与可能抛出的路由组合使用。

一次 skipInferdiDispose(req) 调用适用于该请求中的所有 InferDI 中间件实例。应用代码必须保留并自行释放每个作用域;req.di 暴露最后一个中间件赋值的作用域。