使用Typescript扩展Express Request对象


126

我正在尝试添加一个属性以使用Typescript从中间件表达请求对象。但是我不知道如何向对象添加额外的属性。如果可能的话,我宁愿不使用括号符号。

我正在寻找一种解决方案,允许我编写与此类似的内容(如果可能):

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

您应该能够使用所需的字段扩展express.d.ts文件提供的请求接口。
toskv

Answers:


143

您想要创建一个自定义定义,并在Typescript中使用一个称为声明合并的功能。这是常用的,例如在中method-override

创建一个文件custom.d.ts,并确保将其包含在您tsconfig.json的-部分中(files如果有)。内容如下所示:

declare namespace Express {
   export interface Request {
      tenant?: string
   }
}

这将允许您在代码中的任何时候使用如下代码:

router.use((req, res, next) => {
    req.tenant = 'tenant-X'
    next()
})

router.get('/whichTenant', (req, res) => {
    res.status(200).send('This is your tenant: '+req.tenant)
})

2
我只是这样做了,但是我没有将我的custom.d.ts文件添加到tsconfig.json的files部分中就可以工作了,但是仍然可以工作。这是预期的行为吗?
Chaim Friedman

1
@ChaimFriedman是的。本files节限制TypeScript包含的文件集。如果您未指定filesinclude,则*.d.ts默认情况下将全部包含在内,因此无需在其中添加自定义类型。
interphx

9
对我Property 'tenant不起作用:我在'Request'类型上不存在`如果我明确地将其包含在内,则没有任何区别tsconfig.jsonUPDATE随着declare global作为@basarat pointet出在他的作品answear,但我不得不这样做import {Request} from 'express'第一。
狮子

5
FWIW,此答案现在已过时。JCM的答案是Request在expressjs(至少4.x)中扩展对象的正确方法
Eric Liprandi

3
对于以后的搜索-我发现一个很好的例子可以直接使用:github.com/3mard/ts-node-example
jd291 '19

79

如中的注释index.d.ts所建议,您只需向全局Express名称空间声明任何新成员。例:

declare global {
  namespace Express {
    interface Request {
      context: Context
    }
  }
}

完整示例:

import * as express from 'express';

export class Context {
  constructor(public someContextVariable) {
  }

  log(message: string) {
    console.log(this.someContextVariable, { message });
  }
}

declare global {
  namespace Express {
    interface Request {
      context: Context
    }
  }
}

const app = express();

app.use((req, res, next) => {
  req.context = new Context(req.url);
  next();
});

app.use((req, res, next) => {
  req.context.log('about to return')
  res.send('hello world world');
});

app.listen(3000, () => console.log('Example app listening on port 3000!'))

我的GitBook涵盖扩展全局名称空间的更多信息


为什么在声明中需要全局?如果不存在怎么办?
杰森·库尔特

这适用于接口,但在任何情况下,需要合并类型,请注意,类型是“封闭”和不能合并:github.com/Microsoft/TypeScript/issues/...
彼得·w ^

@basarat先生,我欠你一些啤酒。
marcellsimon

我还必须添加到tsconfig.json:{“ compilerOptions”:{“ typeRoots”:[“ ./src/typings/”,“ ./node_modules/@types”]},“ files”:[“ ./ src / typings / express / index.d.ts“]}
marcellsimon

以上解决方案均无效..但是此解决方案在第一次运行时就完成了工作..非常感谢.. !!
Ritesh

55

对于较新版本的express,您需要扩展express-serve-static-core模块。

这是必需的,因为现在Express对象来自那里:https : //github.com/DefinitelyTyped/DefinitelyTyped/blob/8fb0e959c2c7529b5fa4793a44b41b797ae671b9/types/express/index.d.ts#L19

基本上,使用以下命令:

declare module 'express-serve-static-core' {
  interface Request {
    myField?: string
  }
  interface Response {
    myField?: string
  }
}

1
这对我有用,而扩展普通的'express'模块却没有。谢谢!
本·克里格

4
为了这个工作而苦苦挣扎,我也必须导入该模块:import {Express} from "express-serve-static-core";
andre_b

1
@andre_b感谢您的提示。我认为import语句将文件转换为模块,这是必需的部分。我已经切换到使用export {}也可以使用。
Danyal Aytekin

2
确保调用此代码的文件express.d.ts,否则编译器将尝试将其合并为快速的类型,从而导致错误。
汤姆·斯宾塞

3
确保您的类型必须是typeRoots中的第一个!types / express / index.d.ts和tsconfig =>“ typeRoots”:[“ ./src/types”,“ ./node_modules/@types”]
卡亚

30

接受的答案(与其他答案一样)对我不起作用,但是

declare module 'express' {
    interface Request {
        myProperty: string;
    }
}

做到了。希望对别人有帮助。


2
ts文档中 “模块增强”下介绍了类似的方法。如果您不想使用*.d.ts文件,而只是将类型存储在常规*.ts文件中,那就太好了。
im.pankratov '18

3
这也是唯一对我有用的东西,所有其他答案似乎都需要在.d.ts文件中
国会

只要我将custom-declarations.d.ts文件放在TypeScript的项目根目录中,这对我也有效。
focorner

我扩展了原始类型以保留它:import { Request as IRequest } from 'express/index';interface Request extends IRequest。还必须添加typeRoot
Ben Creasy

17

尝试了8个左右的答案但没有成功。我终于设法将它与jd291指向3mards repo的注释一起使用

在名为的库中创建一个文件types/express/index.d.ts。并在其中写道:

declare namespace Express {
    interface Request {
        yourProperty: <YourType>;
    }
}

并将其包含在tsconfig.json

{
    "compilerOptions": {
        "typeRoots": ["./types"]
    }
}

然后yourProperty应可在每个请求下访问:

import express from 'express';

const app = express();

app.get('*', (req, res) => {
    req.yourProperty = 
});

14

提供的解决方案都不适合我。我最终只是扩展了Request接口:

import {Request} from 'express';

export interface RequestCustom extends Request
{
    property: string;
}

然后使用它:

import {NextFunction, Response} from 'express';
import {RequestCustom} from 'RequestCustom';

someMiddleware(req: RequestCustom, res: Response, next: NextFunction): void
{
    req.property = '';
}

编辑:TypeScript的最新版本对此有所抱怨。相反,我必须这样做:

someMiddleware(expressRequest: Request, res: Response, next: NextFunction): void
{
    const req = expressRequest as RequestCustom;
    req.property = '';
}

1
这将起作用,但是如果您具有100的中间件功能,则非常冗长,amirite
Alexander Mills,

1
@ user2473015是的,最近版本的Typescript打破了这一点。看到我更新的答案。
汤姆·梅塔姆

8

在TypeScript中,接口是开放式的。这意味着您可以从任何地方向它们添加属性,只需重新定义它们即可。

考虑到您正在使用这个express.d.ts文件,您应该能够重新定义Request接口以添加额外的字段。

interface Request {
  property: string;
}

然后,在您的中间件函数中,req参数也应具有此属性。您应该能够使用它,而无需对代码进行任何更改。


1
您如何在整个代码中“共享”该信息?如果我请求定义一个属性,比如Request.user = {};app.ts如何userController.ts认识呢?
Nepoxx

2
@Nepoxx如果重新定义接口,则编译器将合并属性并使它们在所有位置可见,这就是为什么。理想情况下,您将在.d.ts文件中进行重新定义。:)
toskv

这似乎可行,但是,如果我使用类型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 '16

5

虽然这是一个非常老的问题,但最近我偶然发现了这个问题。可以接受的答案很好,但是我需要向其中添加一个自定义接口Request-我在代码中一直在使用的接口,但与接受的接口不能很好地工作回答。逻辑上,我尝试了这个:

import ITenant from "../interfaces/ITenant";

declare namespace Express {
    export interface Request {
        tenant?: ITenant;
    }
}

但这是行不通的,因为Typescript将.d.ts文件视为全局导入,并且当其中包含导入时,它们将被视为普通模块。这就是为什么上面的代码不适用于标准打字稿设置的原因。

这就是我最终要做的

// typings/common.d.ts

declare namespace Express {
    export interface Request {
        tenant?: import("../interfaces/ITenant").default;
    }
}
// interfaces/ITenant.ts

export interface ITenant {
    ...
}

这适用于我的主文件,但不适用于我的路由文件或控制器,没有起毛,但是当我尝试编译时,它说“属性'用户'在'请求'类型上不存在”。(我使用的是用户而不是租户),但是如果我在它们上面添加// @ ts-ignore,那么它就可以工作(尽管这当然是解决问题的一种愚蠢的方法。您是否对为什么不这样做有任何想法?工作我的其他文件吗
洛根

@Logan这是很奇怪的事情。你可以分享你的.d.tstsconfig.json以及使用实例?另外,由于从TS 2.9开始仅支持在全局模块中进行导入,因此您正在使用什么版本的打字稿?这可能会更好。
19kb

我已经在此处上传了数据,pastebin.com/0npmR1Zr我不确定为什么高亮显示都被弄乱了,尽管这是来自主文件prnt.sc/n6xsyl 这是来自另一个文件prnt.sc/n6xtp0 显然是它了解发生了什么,但是编译器却不知道。我正在使用打字稿的3.2.2版本
Logan

1
令人惊讶的是,它对... "include": [ "src/**/*" ] ...我有用, 但对我"include": ["./src/", "./src/Types/*.d.ts"],无效。我还没有深入尝试了解这一点
19kb

通过使用动态导入来导入界面对我来说很有效。谢谢
Roman Mahotskyi

3

也许这个问题已经解决了,但我只想分享一点,现在有时候像其他答案一样的界面可能有点过于严格,但是我们实际上可以维护所需的属性,然后通过创建一个与一种类型的键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();
});

因此,现在,我们还可以向该对象添加任何其他想要的属性。


2

如果您正在寻找适用于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参考


2

所有这些响应似乎都是错误的,或者在某种程度上已经过时了。

这对我来说是2020年5月的工作:

${PROJECT_ROOT}/@types/express/index.d.ts

import * as express from "express"

declare global {
    namespace Express {
        interface Request {
            my_custom_property: TheCustomType
        }
    }
}

在中tsconfig.json,添加/合并属性,使得:

"typeRoots": [ "@types" ]

干杯。


与Webpack + Docker一起使用,可以将import *替换为export {};
Dooomel

1

一种可能的解决方案是对任何对象使用双重铸造

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()
})

双重铸造的优点是:

  • 可以打字
  • 它不会污染现有的定义,但会扩展它们,避免造成混淆
  • 由于强制类型转换是显式的,因此会使用-noImplicitany标志来编译罚款

另外,还有一条快速(无类型)路线:

 req['myProperty'] = setProperty()

(请勿使用您自己的属性编辑现有的定义文件-这是无法维护的。如果定义错误,请打开请求请求)

编辑

参见下面的评论,在这种情况下,简单的铸造作品 req as MyRequest


@akshay在这种情况下,是的,因为MyRequest扩展了http.IncomingMessage。事实并非如此,any唯一的选择是双浇铸
Bruno Grieder

建议您转换为未知,而不是任何。
开发人员

0

这个答案对那些依赖npm package的人是有益的ts-node

我也一直在为扩展请求对象而苦恼,我在堆栈溢出中遵循了很多答案,并最终遵循了以下提到的策略。

我在以下目录中声明了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无法找到请求对象的扩展类型定义。

最终,在花了几个小时之后,据我所知,VS Code并没有抱怨并能够找到类型定义,这暗示ts-node编译器出了点问题。

更新开始scriptpackage.json固定的对我来说。

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

--files参数方面发挥关键作用在这里找到确定的自定义类型定义。

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


0

在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文件,以将“ --files”命令添加到ts-node,例如:
{
  "restartable": "rs",
  "ignore": [".git", "node_modules/**/node_modules"],
  "verbose": true,
  "exec": "ts-node --files",
  "watch": ["src/"],
  "env": {
    "NODE_ENV": "development"
  },
  "ext": "js,json,ts"
}

0

这个答案可能已经很晚了,但是无论如何,这就是我的解决方法:

  1. 确保tsconfig文件中包含类型源(这可能是一个全新的线程)
  2. 在您的类型目录内添加一个新目录,并将其命名为您要扩展或为其创建类型的包。在这种情况下,您将创建一个目录,名称为express
  3. express目录内创建一个文件并命名index.d.ts(必须完全一样)
  4. 最后,要扩展类型,您只需要添加如下代码:
declare module 'express' {
    export interface Request {
        property?: string;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.