使用Typescript扩展Express Request对象

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

我正在寻找一个解决方案,允许我写类似于这个(如果可能):

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

一个可能的解决方案是使用“double casting to any”

用你的属性定义一个接口

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

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

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

interface Request {
property: string;
}

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

你想要创建一个自定义定义,并使用Typescript中名为宣布合并的特性。这是常用的,例如在method-override中。

创建一个文件custom.d.ts,确保它包含在你的tsconfig.json的__abc2节中(如果有的话)。内容如下所示:

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

正如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!'))

更多的

扩展全局名称空间也包括在TypeScript Deep Dive中

他们提供的解决方案没有一个对我有效。我最终只是扩展了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 = '';
}

编辑:取决于你的tsconfig,你可能需要这样做:

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

公认的答案(和其他答案一样)对我不适用,但是

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

所做的。希望这能帮助到一些人。

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

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


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

但这并没有起作用,因为Typescript将.d.ts文件视为全局导入,当它们中有导入时,它们被视为普通模块。这就是为什么上面的代码不能在标准的typescript设置下工作。

这是我最后做的事情

// typings/common.d.ts


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


export interface ITenant {
...
}

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

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

如果您正在寻找与express4一起工作的解决方案,下面是:

@types /快递/ index.d。--------必须是/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

在尝试了8个左右的答案,没有成功。我最终设法让它与jd291的注释指向3 mards回购工作。

在基类中创建一个名为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 =
});

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

这在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" ]

欢呼。

这个答案将有利于那些依赖npm包ts-node的人。

我也在与扩展请求对象的相同问题作斗争,我在stack-overflow &最后遵循下面提到的策略。

我在下面的目录中声明了表达的扩展类型。${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编译器有问题。

package.json中更新开始script为我修复了它。

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

--files参数在这里起着决定自定义类型定义的关键作用。

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

为了帮助那些只是在这里寻找其他尝试的人,这是我在2020年5月底试图扩展ExpressJS的Request时所做的工作。我不得不尝试了十几件事,才让这个工作:

  • 在tsconfig的“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"
}

现在回答这个问题可能已经很晚了,但无论如何,我是这样解决的:

  1. 确保你的类型源包含在你的tsconfig文件中(这可能是一个全新的线程)
  2. 在types目录中添加一个新目录,并将其命名为要为其扩展或创建类型的包。在本例中,您将创建一个名为express的目录
  3. express目录中创建一个文件并将其命名为index.d.ts(必须与此完全相同)
  4. 最后,为了扩展类型,你只需要放如下代码:
declare module 'express' {
export interface Request {
property?: string;
}
}

这就是我在使用Nestjs和Express时的工作方式。截至2020年11月。

创建一个文件:./@types/express- server -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",
]

在mac节点12.19.0和express 4上,使用护照进行身份验证,我需要扩展我的Session对象

与上面相似,但略有不同:

import { Request } from "express";




declare global {
namespace Express {
export interface Session {
passport: any;
participantIds: any;
uniqueId: string;
}
export interface Request {
session: Session;
}
}
}


export interface Context {
req: Request;
user?: any;
}```

可选择的解决方案

这实际上并没有直接回答这个问题,但我提供了一个替代方案。我也在与同样的问题作斗争,并尝试了该页面上几乎所有的界面扩展解决方案,但没有一个有效。

这让我停下来思考:为什么我实际上在修改请求对象?"

使用response.locals

Express开发人员似乎认为用户可能希望添加自己的属性。这就是为什么存在locals对象的原因。问题是,它不在request中,而是在response对象中。

response.locals对象可以包含你想要的任何自定义属性,封装在请求-响应循环中,因此不会暴露给来自不同用户的其他请求。

需要存储userId?只需设置response.locals.userId = '123'。没有必要纠结于打字。

它的缺点是,您必须传递响应对象,但很可能您已经这样做了。

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

打字

另一个缺点是缺乏类型安全性。然而,你可以在Response对象上使用泛型类型来定义bodylocals对象的结构:

Response<MyResponseBody, MyResponseLocals>

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

警告

你不能保证userId属性确实在那里。您可能希望在访问它之前进行检查,特别是在对象的情况下。

使用上面的例子来添加一个userId,我们可以有这样的东西:

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
}
);
对我来说,简单的解决方案是创建一个新的自定义接口扩展express Request。 这个接口应该包含所有可选的自定义req属性。 将此接口设置为中间件请求的类型
// ICustomRequset.ts
import { Request } from "express"
export default interface ICustomRequset extends Request {
email?: string;
roles?: Array<string>;
}


// AuthMiddleware.ts
...
export default async function (
req: ICustomRequset,
res: Response,
next: NextFunction
) {
try {
// jwt code
req.email=jwt.email
req.roles=jwt.roles
next()
}catch(err){}

在2021年,这个方法是有效的:

在express 4.17.1中,https://stackoverflow.com/a/55718334/9321986https://stackoverflow.com/a/58788706/9321986的组合工作:

types/express/index.d.ts:

declare module 'express-serve-static-core' {
interface Request {
task?: Task
}
}

tsconfig.json中:

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

如果你尝试了所有的答案,仍然没有得到工作,这里有一个简单的hack

app.use((req, res, next) => {
(req as any).property = setProperty();
next();
});
这将把req对象转换为any,因此你可以添加任何你想要的属性。 请记住,这样做将失去req对象的类型安全性

这招对我很管用:

declare namespace e {
export interface Request extends express.Request {
user:IUserReference,
[name:string]:any;
}
export interface Response extends express.Response {
[name:string]:any;
}
}






export type AsyncRequestHandler = (req:e.Request, res:e.Response, logger?:Logger) => Promise<any>|Promise<void>|void;
export type AsyncHandlerWrapper = (req:e.Request, res:e.Response) => Promise<void>;

我在代码中使用了它,比如以这样的方式导出具有这样签名的函数:

app.post('some/api/route', asyncHandlers(async (req, res) => {
return await serviceObject.someMethod(req.user, {
param1: req.body.param1,
paramN: req.body.paramN,
///....
});
}));

只需将属性添加到req。params对象。

req.params.foo = "bar"

我使用response.locals对象来存储新属性。下面是完整的代码

export function testmiddleware(req: Request, res: Response, next: NextFunction) {
res.locals.user = 1;
next();
}


// Simple Get
router.get('/', testmiddleware, async (req: Request, res: Response) => {
console.log(res.locals.user);
res.status(200).send({ message: `Success! ${res.locals.user}` });
});

我也有同样的问题,我是这样解决的:

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

但有一些条件是必须的!

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

在此之后,tsc将构建bundle,但ts-node不会。

  1. 必须将--files添加到编译器命令中
ts-node --files src/server.ts

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

import { Request } from 'express'
    

type CustomRequest = Request & { userId?: string }

必须使用带有可选的(?)操作符的扩展属性来避免出现“没有重载匹配此调用”错误。

软件包版本:

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

对于简单的情况,我在外层中间件中使用headers属性,稍后在内部中间件中获得它。

// outer middleware
req.headers["custom_id"] = "abcxyz123";


// inner middleware
req.get("custom_id");


缺点:

  • 只能存储string。如果你想存储其他类型,如jsonnumber,你可能必须在以后解析它。
  • headers属性没有文档化。Express只记录req.get()方法。所以你必须使用与属性headers一起工作的Express的确切版本。

我已经改变了响应类型,包括ApiResponse(自定义响应对象)Response<ApiResponse>

export interface ApiResponse {
status?: string
error?: string
errorMsg?: string
errorSubject?: string
response?: any
}


const User = async (req: Request, res: Response<ApiResponse>, next: NextFunction) => {
try {
if (!username) return res.status(400).send({ errorMsg: 'Username is empty' })
/* ... */
} catch(err){
/* ... */
}
}

.d。Ts声明是黑客。简单地接受这样一个事实:express的中间件系统不适合typescript。所以不要用它。

错误代码示例:

const auth = (req) => {
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
}))

只要使用语言,而不是表达促进的不安全突变。

编辑:我使用第一个建议的解决方案。但不同的是,我的auth函数抛出了一个错误,我可以捕捉并返回正确的响应,所以我不需要在控制器中这样做。例如:

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

我还发送返回类型,而不是手动调用res.send。它还允许我输入响应。我建议你也去看看tRPC