使用Typescript扩展Express Request对象

334

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

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

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

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

15

如果您正在寻找与Express4兼容的解决方案,则在这里:

@types/express/index.d.ts: --------必须是/index.d.ts

declare namespace Express { // must be namespace, and not declare module "Express" { 
  export interface Request {
    user: any;
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2016",
    "typeRoots" : [
      "@types", // custom merged types must be first in a list
      "node_modules/@types",
    ]
  }
}

参考自https://github.com/TypeStrong/ts-node/issues/715#issuecomment-526757308


Cannot find module 'express' or its corresponding type declarations. - RedGuy11
谢谢。花了两天时间尝试修复这个问题。 - Moaaz Bhnas
这是正确的回答。 - undefined

12

我通过创建一个新类型而不是全局扩展Request类型来解决了这个问题。

import { Request } from 'express'
    
type CustomRequest = Request & { userId?: string }

你必须使用可选运算符与扩展属性,以避免出现“No overload matches this call”错误。

包版本:

    "@types/express": "^4.17.13",
    "@types/morgan": "^1.9.3",
    "@types/node": "^17.0.29",
    "typescript": "^4.6.3",
    "express": "^4.18.0",

2
就我个人而言,我更喜欢这个解决方案,因为它更符合 TypeScript 的风格,而命名空间的变体则更像是扩展版的 JavaScript。 - Alec Mather
1
它很好地解决了。谢谢。 - Niyongabo Eric

12

对于那些依赖npm包ts-node的人,这个答案将是有益的。

我曾经也遇到过扩展request对象的问题,我查阅了很多stackoverflow上的答案,最终采用了以下策略。

我在以下目录中声明了express的扩展类型:${PROJECT_ROOT}/api/@types/express/index.d.ts

declare namespace Express {
  interface Request {
    decoded?: any;
  }
}

然后将我的tsconfig.json更新为类似于这样的内容。

{
  "compilerOptions": {
     "typeRoots": ["api/@types", "node_modules/@types"]
      ...
  }
}

即使按照上述步骤执行后,Visual Studio不再抱怨,但遗憾的是ts-node编译器仍然会抛出异常。

 Property 'decoded' does not exist on type 'Request'.

显然,ts-node 无法定位request对象的扩展类型定义。

最终经过几个小时的研究,我发现 VS Code 没有报错且能够找到类型定义,这意味着 ts-node 编译器出了问题。

package.json 中更新启动 script 后问题得以解决。

"start": "ts-node --files api/index.ts",

--files参数在确定自定义类型定义方面发挥了关键作用。

如需更多信息,请访问:https://github.com/TypeStrong/ts-node#help-my-types-are-missing


4
对我来说,--files 标志是 ts-node 中缺失的一部分,这也是为什么我无法让合并的类型对我起作用的原因。 - David Pooley
1
这个是赢家! - kinoli
非常感谢。我也遇到了同样的问题。对我来说,使用--files也起作用了。 - undefined

12

以下是我在使用Nestjs和Express时所使用的方法。时间为2020年11月。

创建文件:./@types/express-serve-static-core/index.d.ts

注意:必须要使用以上路径和文件名才能让TypeScript声明合并工作。

import { UserModel } from "../../src/user/user.model";

declare global{
    namespace Express {
        interface Request {
            currentUser: UserModel
        }
    }
}

将此内容添加到您的tsconfig.json文件中

"typeRoots": [
      "@types",
      "./node_modules/@types",
    ]        

2
由于某种原因,只有你的解决方案_几乎_对我起作用。只是除非我直接声明“Express”而不是“global”,否则它将无法工作。 - Danry
@Danry 只有在同一 *.d.ts 文件中导入任何模块时才需要使用 declare global - Kay
令人惊讶的是,没有一个解决方案是“完美无缺的”。 - Bersan

10
在TypeScript中,接口是开放式的。这意味着您可以通过重新定义它们从任何地方向其中添加属性。
考虑到您正在使用此express.d.ts文件,您应该能够重新定义Request接口以添加额外的字段。
interface Request {
  property: string;
}

然后在你的中间件函数中,req 参数也应该具有这个属性。你应该能够在不更改代码的情况下使用它。


1
你如何在代码中“共享”信息?如果我在app.ts中定义了一个在Request中的属性,如Request.user = {};,那么userController.ts该怎么知道它呢? - Nepoxx
2
@Nepoxx 如果你重新定义一个接口,编译器会合并属性并使它们在任何地方都可见,这就是为什么。理想情况下,你应该在一个 .d.ts 文件中进行重新定义。 :) - toskv
1
这似乎是有效的,但是如果我使用类型 express.Handler(而不是手动指定 (req: express.Request, res: express.Response, next: express.NextFunction) => any)),它似乎不引用相同的 Request,因为它抱怨我的属性不存在。 - Nepoxx
我不太期望它会,除非 express.Handler 扩展了 Request 接口。它扩展了吗? - toskv
2
如果我使用 declare module "express",那么我可以让它工作,但如果我使用 declare namespace Express,就不行了。我更喜欢使用命名空间语法,但它对我来说根本不起作用。 - WillyC
显示剩余2条评论

5

为了帮助任何只是想尝试其他方法的人,以下是我在2020年5月底尝试扩展ExpressJS请求时所使用的方法。在成功之前,我试过十几种不同的方法:

  • 颠倒tsconfig.json中"typeRoots"的顺序(如果你在tsconfig中设置了rootDir,比如"./src",则不要忘记删掉src路径)。例子:
"typeRoots": [
      "./node_modules/@types",
      "./your-custom-types-dir"
]
  • 自定义扩展示例('./your-custom-types-dir/express/index.d.ts")。我需要使用内联导入和默认导出来将类用作类型,因此也显示了它:
declare global {
  namespace Express {
    interface Request {
      customBasicProperty: string,
      customClassProperty: import("../path/to/CustomClass").default;
    }
  }
}
  • 更新您的nodemon.json文件,以在ts-node中添加“--files”命令,例如:
{
  "restartable": "rs",
  "ignore": [".git", "node_modules/**/node_modules"],
  "verbose": true,
  "exec": "ts-node --files",
  "watch": ["src/"],
  "env": {
    "NODE_ENV": "development"
  },
  "ext": "js,json,ts"
}

2
我来自2021年。仍然不起作用。 - Jora

5

我也遇到同样的问题,解决方法如下:

// /src/types/types.express.d.ts
declare namespace Express {
    export interface Request {
        user: IUser
    }
}

但是需要满足一些条件!

  1. 将配置添加到 tsconfig.json 文件中
"paths": {
    "*": [
        "node_modules/*",
        "src/types/*"
    ]
},

执行tsc命令可以构建捆绑包,但是ts-node无法进行构建。

  1. 您必须在编译命令中添加--files参数。
ts-node --files src/server.ts

1
这对我有用,除了第二部分 - 我将路径添加到了我的tsconfig文件的typeRoots属性中。 "typeRoots": [ "./node_modules/*", "./src/types/*" ] - TJBlackman

5

一种可能的解决方案是使用“双重转换到任何类型”

1- 使用您的属性定义接口

export interface MyRequest extends http.IncomingMessage {
     myProperty: string
}

2- 双重转换

app.use((req: http.IncomingMessage, res: http.ServerResponse, next: (err?: Error) => void) => {
    const myReq: MyRequest = req as any as MyRequest
    myReq.myProperty = setProperty()
    next()
})

双重转换的优点在于:
  • 可用 typings
  • 它不会污染现有定义,但扩展它们,避免混淆
  • 由于转换是显式的,因此可以使用 -noImplicitany 标志进行很好的编译

或者,还有一种快速(未经类型化)的方法:

 req['myProperty'] = setProperty()

请不要用自己的属性编辑现有的定义文件,这是不可维护的。如果定义不正确,请打开一个拉取请求。

编辑

参见下面的评论,简单的强制类型转换在这种情况下有效 req as MyRequest


@akshay 在这种情况下,是的,因为 MyRequest 扩展了 http.IncomingMessage。如果不是这种情况,通过 any 进行双重转换将是唯一的选择。 - Bruno Grieder
建议您将类型转换为 unknown 而不是 any。 - dev
像这样的强制转换不幸地需要在链中的每个后续函数中重复进行强制转换。例如middleware1,middleware2,middleware3和路由本身。但是这是我发现的唯一实现,它实现了与上下文相关的类型,而不仅仅是全局扩展“Request”并将所有可能的属性放在其中。 - defraggled

5
也许这个问题已经得到回答,但我想分享一点,现在有时像其他答案一样的接口可能会有一些限制,但我们实际上可以维护所需的属性,然后创建一个类型为string且值类型为any的键来添加任何其它需要添加的属性。
import { Request, Response, NextFunction } from 'express'

interface IRequest extends Request {
  [key: string]: any
}

app.use( (req: IRequest, res: Response, next: NextFunction) => {
  req.property = setProperty();

  next();
});

现在,我们可以向这个对象添加任何其他属性。


这个对我起作用了。 - Archil Labadze
当我在我的tsconfig.json中使用Strict: true时,这给我带来了错误。尽管它在运行时工作正常。 - undefined

5
如果你尝试了所有答案,但仍然无法使其工作,这里有一个简单的解决办法。
app.use((req, res, next) => {
    (req as any).property = setProperty(); 
    next();
});

这将把req对象转换为any,因此您可以添加任何属性。 请记住,这样做会使得req对象失去类型安全性。

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