使用Typescript扩展Express Request对象

334

我正在尝试使用typescript在中间件中添加一个表示请求对象的属性。但是我无法弄清如何向该对象添加额外属性。如果可能,我希望不使用方括号表示。

我正在寻找一种解决方案,使我可以编写类似于以下内容的代码(如果可能):

app.use((req, res, next) => {
    req.property = setProperty(); 
    next();
});

1
你应该能够通过扩展 express.d.ts 文件提供的请求接口来添加所需的字段。 - toskv
32个回答

-2
用.d.ts声明来解决这个问题是一种hack。接受这样一个事实,express的中间件系统不适用于typescript。我建议不要使用它们。
如果你使用.d.ts,你会失去编译时检查的功能。你可能期望某些东西在那里,即使它实际上并不存在。或者你可以将其定义为可选的,但这样你就必须每次都检查它是否存在。我们使用typescript来在编译时捕获错误,但.d.ts文件对我们没有帮助。它们不能成为解决方案。
示例express中间件(不推荐使用):
const auth = (req, res) => {
  const user = // parse user from the header

  if(!user)
     return res.status(401).send({ result: 'unauthorized-error' })

  req.user = user
  return next()
}

app.get('/something', auth, (req, res) => {
  // do something
})

更好的代码:
const auth = (req) => {
  return // parse user from the header
}

app.get('/something', (req, res) => {
  const user = auth(req)
  if(!user)
    return res.status(401).send({ result: 'unauthorized-error' })
  // do something
})

你可以通过高阶函数来恢复中间件的使用方式。

const auth = (req) => {
    return // parse user from the header
}

const withUser = (callback: (foo, req, res) => void) => (req, res) => {
    const user = auth(req)
    if(!user)
        return res.status(401).send({ result: 'unauthorized-error' })
    return callback(user, req, res)
}

app.get('/something', withUser((user, req, res) => {
    // do something
}))

你甚至可以堆叠它们,如果你想的话。

const withFoo = (callback) => (req, res) => { /* ... */ }
const withBar = (callback) => (req, res) => { /* ... */ }
const withBaz = (callback) => (req, res) => { /* ... */ }

const withFooBarAndBaz = (callback) => (req,res) => {
    withFoo((foo) =>
        withBar((bar) =>
            withBaz((baz) =>
                callback({ foo, bar, baz }, req, res)
            )(req,res)
        )(req,res)
    )(req,res)
}

app.get('/something', withFooBarAndBaz(({ foo, bar, baz }, req, res) => {
    // do something with foo, bar and baz
}))

您可以使用语言代替不安全的变异来表达促进。

在我目前的解决方案之前,我使用了最初提出的方法来实现中间件,但有所不同的是我的身份验证函数会抛出错误,我可以捕获并返回正确的响应,所以我不需要在控制器中处理它。例如:

app.get('/something', withResponse((req) => {
   const user = auth(req)
   return success({
     message: `Hi ${user.name}`
   })
}))

我还返回了响应,而不是手动调用res.send。这样我可以直接输入响应。
我的当前解决方案更进一步,我可以自动推断每个返回类型,并立即在前端使用。关于此,请查看tRPC
如果您想保留类似REST的API结构,并提供额外的类型安全性保证,请查看我的tRPC替代方案:Cuple RPC

你可能误读了。这是一个以 (req, res) => {} 开头的函数声明,它是一个有效的 Express 控制器。这是一个嵌套函数。 - Dávid Bíró
嵌套的高阶函数在我看来是代码异味- 你会遇到回调地狱和非常长的缩进行,这些行中包含难以进行断点调试的垃圾代码。这就是为什么我们现在有了依赖注入(它相当笨重)。如果你想像这样编码(这在某种程度上还可以,但不适合JS)- 去尝试.NET Core吧。 - PathToLife
@DávidBíró - 谢谢。那么,(callback: (foo, req, res) => void) => (req, res) => {} 应该被理解为 (callback: (foo, req, res) => void) => ((req, res) => {}) 吗?也就是说,这是一个接受 callback 并返回函数 (req, res) => {} 的函数? - Shuzheng
@Shuzheng 是的,就是这样。 - Dávid Bíró
@PathToLife 我同意,但对于类型系统来说很容易。我个人使用这个提议的解决方案,这种回调地狱是不太可能发生的。通常我们只有一个身份验证中间件。就这些了。我展示这些变体只是以防万一。DI 有它自己的问题,我个人喜欢避免使用它,因为我认为没有它更容易实现新功能。(如果我被允许替换 js 的话,我会在第一时间这样做。) - Dávid Bíró
显示剩余4条评论

-5

只需将该属性添加到req.params对象中即可。

req.params.foo = "bar"

2
你的回答可以通过添加额外支持信息来改进。请[编辑]以添加更多细节,如引用或文档,以便他人可以确认你的回答是否正确。您可以在帮助中心找到有关编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接