使用Typescript扩展Express Request对象

Isa*_*ren 78 node.js express typescript

我正在尝试使用typescript添加一个属性来表示来自中间件的请求对象.但是,我无法弄清楚如何向对象添加额外的属性.如果可能的话,我宁愿不使用括号表示法.

我正在寻找一种解决方案,允许我写一些类似的东西(如果可能的话):

app.use((req, res, next) => {
    req.property = setProperty(); 
    next();
});
Run Code Online (Sandbox Code Playgroud)

小智 96

您想要创建自定义定义,并在Typescript中使用名为声明合并的功能.这通常用于例如method-override.

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

declare namespace Express {
   export interface Request {
      tenant?: string
   }
}
Run Code Online (Sandbox Code Playgroud)

这将允许您在代码中的任何位置使用以下内容:

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)
})
Run Code Online (Sandbox Code Playgroud)

  • @EricLiprandi JCM 是谁?您指的答案在哪里?引用其他答案时请链接。名称可能会随着时间而改变。 (16认同)
  • FWIW,这个答案现在已经_过时_。JCM的答案是在expressjs中增强“Request”对象的正确方法(至少4.x) (10认同)
  • 不适合我:我得到'属性'租户`在'请求'类型上不存在`如果我明确地将它包含在`tsconfig.json`中,则没有区别.**更新**在他的answear作品中将`declare global`改为@basarat pointet,但我必须首先从'express'`执行`import {Request}. (5认同)
  • 对于未来的搜索 - 我发现一个开箱即用的好例子:https://github.com/3mard/ts-node-example (4认同)
  • @devklick 这是一个非常好的问题!我想答案已经被删除了......我已经好几年没有处理过这个问题了。但我认为 basarat 的[答案](/sf/answers/3321394051/) 是权威。他是个大人物;) (3认同)
  • 我只是这样做了,但我没有将我的 custom.d.ts 文件添加到我的 tsconfig.json 中的 files 部分就让它工作了,但它仍然有效。这是预期的行为吗? (2认同)
  • JCM 的答案从 Request 对象中删除了现有属性。Kaleidawave 的答案反而有效 (2认同)

kal*_*ave 55

在尝试了 8 个左右的答案但没有成功之后。我终于设法让它与jd291指向3mards repo的评论一起工作

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

declare namespace Express {
    interface Request {
        yourProperty: <YourType>;
    }
}
Run Code Online (Sandbox Code Playgroud)

并将其包含在tsconfig.json

{
    "compilerOptions": {
        "typeRoots": ["./types"]
    }
}
Run Code Online (Sandbox Code Playgroud)

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

{
    "compilerOptions": {
        "typeRoots": ["./types"]
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 需要将命名空间包装在“declare global {}”中,这样​​就可以了。 (6认同)
  • 2021 年,这对我有用。 (4认同)
  • 适用于 Express v4.17.1 和 typescript v4.3.4 (3认同)

bas*_*rat 50

您只需向全局index.d.ts命名空间声明任何新成员.例:

declare global {
  namespace Express {
    interface Request {
      context: Context
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

完整示例:

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!'))
Run Code Online (Sandbox Code Playgroud)

更多

此处介绍了扩展全局命名空间:https://basarat.gitbooks.io/typescript/docs/types/lib.d.ts.html

  • 为什么声明中需要 global ?如果它不在那里会发生什么? (2认同)

anl*_*nli 41

2023 年,这一功能将发挥作用:

在 Express 4.17.1 中, /sf/answers/3900283411//sf/answers/4115209451/的组合有效:

types/express/index.d.ts

declare module 'express-serve-static-core' {
    interface Request {
        task?: Task
    }
}
Run Code Online (Sandbox Code Playgroud)

并在tsconfig.json

{
    "compilerOptions": {
        "typeRoots": ["./types"]
    }
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句:如果你想这样做,请三思!当我不了解 TDD 时我就这样做了 - 现在我无法想象在日常工作中不使用代码测试 - 并且“污染”请求对象并让它在应用程序中徘徊会增加您需要的机会模拟大量测试的要求。也许最好阅读Clean CodeSOLIDIOSPIOSP 2.0IODA以及类似的内容,并理解为什么向请求添加属性以使其在“应用程序的另一端”可用可能不是最好的主意。

  • 对我不起作用,并导致我的整个项目出现错误,因为它开始导入错误的“Request”。你的两个参考文献都与问题本身相关 (3认同)

max*_*-lt 21

接受的答案(和其他人一样)对我不起作用

declare module 'express' {
    interface Request {
        myProperty: string;
    }
}
Run Code Online (Sandbox Code Playgroud)

没有.希望能帮到别人.

  • [ts docs](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) 在“模块扩充”下描述了类似的方法。如果您不想使用 `*.d.ts` 文件而只想将您的类型存储在常规的 `*.ts` 文件中,那就太好了。 (2认同)
  • 这是唯一对我有用的东西,所有其他答案似乎都需要在.d.ts文件中 (2认同)
  • 我扩展了原始类型以保留它:“import {Request as IRequest } from 'express/index';”和“interface Request extends IRequest”。还必须添加 typeRoot (2认同)

JCM*_*JCM 18

对于较新版本的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
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 确保您的类型必须位于 typeRoots 的第一位!types/express/index.d.ts 和 tsconfig =&gt; "typeRoots": ["./src/types", "./node_modules/@types"] (5认同)
  • 这对我有用,而扩展普通的“express”模块却没有。谢谢! (2认同)
  • 为了这个工作,我不得不为此苦苦挣扎,我还必须导入模块:```从“ express-serve-static-core”中导入{Express}; (2认同)
  • @andre_b 感谢您的提示。我认为 import 语句将文件转换为模块,这是必要的部分。我已改用“export {}”,这也有效。 (2认同)
  • 确保此代码所在的文件_不_称为“express.d.ts”,否则编译器将尝试将其合并到快速类型中,从而导致错误。 (2认同)

Wil*_*ner 17

所有这些回应似乎在某种程度上都是错误的或过时的。

这在 2020 年 5 月对我有用:

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

import * as express from "express"

declare global {
    namespace Express {
        interface Request {
            my_custom_property: TheCustomType
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在 中tsconfig.json,添加/合并属性,以便:

"typeRoots": [ "@types" ]
Run Code Online (Sandbox Code Playgroud)

干杯。

  • 不起作用。```类型'Request'上不存在属性'user'。``` (4认同)

小智 16

我通过创建一个新类型解决了这个问题,而没有全局扩展 Request 类型。

import { Request } from 'express'
    
type CustomRequest = Request & { userId?: string }
Run Code Online (Sandbox Code Playgroud)

您必须将扩展属性与可选(?)运算符一起使用,以免出现“没有与此调用匹配的重载”错误。

封装版本:

    "@types/express": "^4.17.13",
    "@types/morgan": "^1.9.3",
    "@types/node": "^17.0.29",
    "typescript": "^4.6.3",
    "express": "^4.18.0",
Run Code Online (Sandbox Code Playgroud)

  • 就我个人而言,我最喜欢这个解决方案,因为它感觉更像打字稿风格,命名空间变体看起来更像扩展 JavaScript 风格。 (2认同)

Div*_*wat 13

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

我也在努力解决扩展请求对象的同样问题,我遵循了堆栈溢出中的很多答案,并最终遵循了下面提到的策略。

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

declare namespace Express {
  interface Request {
    decoded?: any;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

{
  "compilerOptions": {
     "typeRoots": ["api/@types", "node_modules/@types"]
      ...
  }
}
Run Code Online (Sandbox Code Playgroud)

即使完成上述步骤后,Visual Studio 也不再抱怨,但不幸的是,ts-node编译器仍然习惯抛出异常。

 Property 'decoded' does not exist on type 'Request'.
Run Code Online (Sandbox Code Playgroud)

显然,ts-node无法找到请求对象的扩展类型定义。

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

更新开始为我修复scriptpackage.json它。

"start": "ts-node --files api/index.ts",
Run Code Online (Sandbox Code Playgroud)

参数--files在这里起着关键作用,以确定自定义类型定义。

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

  • ts-node 的 `--files` 标志是缺失的部分,这就是为什么我无法让合并类型为我工作的原因。 (4认同)

16k*_*6kb 11

虽然这是一个非常古老的问题,但我最近偶然发现了这个问题。接受的答案工作正常,但我需要添加一个自定义界面Request- 我在我的代码中一直使用的界面,但与接受的界面效果不佳回答。从逻辑上讲,我试过这个:

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

declare namespace Express {
    export interface Request {
        tenant?: ITenant;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

这是我最终做的

// typings/common.d.ts

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

export interface ITenant {
    ...
}
Run Code Online (Sandbox Code Playgroud)


Aaa*_*ron 11

Alternative solution

This is not actually answering to the question directly, but I'm offering an alternative. I was struggling with the same problem and tried out pretty much every interface extending solution on this page and none of them worked.

That made me stop to think: "Why am I actually modifying the request object?".

Use response.locals

Express developers seem to have thought that users might want to add their own properties. That's why there is a locals object. The catch is, that it's not in the request but in the response object.

The response.locals object can contain any custom properties you might want to have, encapsulated in the request-response cycle, thus not exposed to other requests from different users.

Need to store an userId? Just set response.locals.userId = '123'. No need to struggle with the typings.

The downside of it is that you have to pass the response object around, but it's very likely that you are doing it already.

https://expressjs.com/en/api.html#res.locals

Typing

Another downside is the lack of type safety. You can, however, use the generic types on the Response object to define what's the structure of the body and the locals objects:

Response<MyResponseBody, MyResponseLocals>

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express/index.d.ts#L127

Caveats

You cannot really guarantee that the userId property is actually there. You might want to check before accessing it, especially in case of objects.

Using the example above to add an userId, we could have something like this:

interface MyResponseLocals {
  userId: string;
}

const userMiddleware = (
  request: Request,
  response: Response<MyResponseBody, MyResponseLocals>,
  next: NextFunction
) => {
  const userId: string = getUserId(request.cookies.myAuthTokenCookie);
  // Will nag if you try to assign something else than a string here
  response.locals.userId = userId;
  next();
};

router.get(
  '/path/to/somewhere',
  userMiddleware,
  (request: Request, response: Response<MyResponseBody, MyResponseLocals>) => {
    // userId will have string type instead of any
    const { userId } = response.locals;

    // You might want to check that it's actually there
    if (!userId) {
      throw Error('No userId!');
    }
    // Do more stuff
  }
);
Run Code Online (Sandbox Code Playgroud)


Tom*_*tam 9

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

import {Request} from 'express';

export interface RequestCustom extends Request
{
    property: string;
}
Run Code Online (Sandbox Code Playgroud)

然后使用它:

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

someMiddleware(req: RequestCustom, res: Response, next: NextFunction): void
{
    req.property = '';
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我认为,@Yusuf 和我遇到了同样的错误: `Type '(req: CustomRequest, res: Response&lt;any, Record&lt;string, any&gt;&gt;) =&gt; Promise&lt;void&gt;' 不可分配给类型 'RequestHandler&lt;ParamsDictionary ,任何,任何,ParsedQs,记录&lt;字符串,任何&gt;&gt;'。参数“req”和“req”的类型不兼容。 (7认同)
  • 这会起作用,但如果您有 100 个中间件函数,则相当冗长,amirite (6认同)
  • “取决于你的 tsconfig” - 取决于 tsconfig 的什么属性?我想相应地更改它以便能够使用界面解决方案。为什么默认情况下这不起作用,对我来说似乎有点违反 OOP 规则。 (5认同)
  • 我更喜欢这种方法,它比在幕后默默地扩展请求对象更明确和清晰。明确哪些属性是您的,哪些来自源模块 (2认同)

tos*_*skv 8

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

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

interface Request {
  property: string;
}
Run Code Online (Sandbox Code Playgroud)

然后在您的中间件函数中,req参数也应具有此属性.您应该能够在不对代码进行任何更改的情况下使用它.

  • @Nepoxx如果重新定义接口,编译器将合并属性并使它们在任何地方都可见,这就是原因.理想情况下,您可以在.d.ts文件中进行重新定义.:) (2认同)
  • 这似乎有效,但是如果我使用类型“express.Handler”(而不是手动指定“(req:express.Request, res:express.Response, next:express.NextFunction)=&gt;any)”),它确实似乎没有引用相同的“Request”,因为它抱怨我的财产不存在。 (2认同)
  • 如果我使用`declare module"express",我可以做到这一点,但如果我使用`declare namespace Express`则不行.我宁愿使用命名空间语法,但它对我不起作用. (2认同)

Cha*_*hea 7

如果您正在寻找适用于 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

tsconfig.json:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2016",
    "typeRoots" : [
      "@types", // custom merged types must be first in a list
      "node_modules/@types",
    ]
  }
}
Run Code Online (Sandbox Code Playgroud)

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


小智 6

如果您尝试了所有答案但仍然无法正常工作,这里有一个简单的技巧

app.use((req, res, next) => {
    (req as any).property = setProperty(); 
    next();
});
Run Code Online (Sandbox Code Playgroud)

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


Bru*_*der 5

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

1-定义与您的属性的接口

export interface MyRequest extends http.IncomingMessage {
     myProperty: string
}
Run Code Online (Sandbox Code Playgroud)

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()
})
Run Code Online (Sandbox Code Playgroud)

双铸件的优点是:

  • 可以打字
  • 它不会污染现有的定义,而是扩展它们,避免混淆
  • 由于转换是明确的,因此它会使用-noImplicitany标志编译罚款

或者,还有快速(无类型)路线:

 req['myProperty'] = setProperty()
Run Code Online (Sandbox Code Playgroud)

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

编辑

请参阅下面的评论,在这种情况下可以进行简单的铸造req as MyRequest


Eka*_*tra 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();
});
Run Code Online (Sandbox Code Playgroud)

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


IfT*_*rue 5

为了帮助那些只是想在这里尝试其他东西的人,这对我在 2020 年 5 月下旬尝试扩展 ExpressJS 的请求时起了作用。在让它发挥作用之前,我必须尝试十几件事:

  • 翻转 tsconfig.json 的“typeRoots”中每个人推荐的顺序(如果您在 tsconfig 中有 rootDir 设置,例如“./src”,请不要忘记删除 src 路径)。例子:
"typeRoots": [
      "./node_modules/@types",
      "./your-custom-types-dir"
]
Run Code Online (Sandbox Code Playgroud)
  • 自定义扩展的示例('./your-custom-types-dir/express/index.d.ts")。根据我的经验,我必须使用内联导入和默认导出来使用类作为类型,因此也显示了:
declare global {
  namespace Express {
    interface Request {
      customBasicProperty: string,
      customClassProperty: import("../path/to/CustomClass").default;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)
  • 更新您的 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"
}
Run Code Online (Sandbox Code Playgroud)

  • 我是2021年的,还是不行 (2认同)

Dav*_*han 5

这就是使用 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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

将此添加到您的 tsconfig.json

"typeRoots": [
      "@types",
      "./node_modules/@types",
    ]        
Run Code Online (Sandbox Code Playgroud)

  • 由于某种原因,只有你的解决方案_几乎_对我有用。只是除非我直接声明“Express”而不使用“global”,否则它将无法工作。 (2认同)

MiF*_*MiF 5

我有同样的问题并这样解决:

// /src/types/types.express.d.ts
declare namespace Express {
    export interface Request {
        user: IUser
    }
}
Run Code Online (Sandbox Code Playgroud)

但需要一些条件!

  1. 添加到tsconfig.json配置
"paths": {
    "*": [
        "node_modules/*",
        "src/types/*"
    ]
},
Run Code Online (Sandbox Code Playgroud)

之后tsc将构建捆绑包,但ts-node不会。

  1. 您必须添加--files到编译器命令
ts-node --files src/server.ts
Run Code Online (Sandbox Code Playgroud)