使用Typescript扩展Express Request对象

时间:2016-05-22 18:01:10

标签: node.js express typescript

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

我正在寻找一种解决方案,可以让我写一些类似的内容(如果可能的话):

05-22 20:32:52.019 24076-24076/com.example.odai.playwithme E/AndroidRuntime: FATAL EXCEPTION: main
         Process: com.example.odai.playwithme, PID: 24076
         java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.odai.playwithme/com.example.odai.playwithme.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
             at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2484)
             at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2544)
             at android.app.ActivityThread.access$900(ActivityThread.java:150)
             at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1394)
             at android.os.Handler.dispatchMessage(Handler.java:102)
             at android.os.Looper.loop(Looper.java:168)
             at android.app.ActivityThread.main(ActivityThread.java:5845)
             at java.lang.reflect.Method.invoke(Native Method)
             at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797)
             at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)
         Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
             at com.example.odai.playwithme.MainActivity.onCreate(MainActivity.java:22)
             at android.app.Activity.performCreate(Activity.java:6248)
             at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1125)
             at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2437)
             at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2544) 
             at android.app.ActivityThread.access$900(ActivityThread.java:150) 
             at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1394) 
             at android.os.Handler.dispatchMessage(Handler.java:102) 
             at android.os.Looper.loop(Looper.java:168) 
             at android.app.ActivityThread.main(ActivityThread.java:5845) 
             at java.lang.reflect.Method.invoke(Native Method) 
             at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797) 
             at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)

23 个答案:

答案 0 :(得分:65)

您想要创建自定义定义,并使用名为Declaration Merging的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)
})

答案 1 :(得分:38)

您只需向全局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!'))

更多

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

答案 2 :(得分:14)

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

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

没有。希望能对某人有所帮助。

答案 3 :(得分:8)

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

考虑到您使用的是此express.d.ts文件,您应该可以重新定义请求界面以添加额外字段。

interface Request {
  property: string;
}

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

答案 4 :(得分:8)

所提供的解决方案都不适合我。我最终只是扩展了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 = '';
}

答案 5 :(得分:7)

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

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

干杯。

答案 6 :(得分:3)

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

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

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

2-double cast

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

答案 7 :(得分:2)

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

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

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

答案 8 :(得分:2)

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

答案 9 :(得分:1)

For newer versions of express, you need to augment the express-serve-static-core module.

This is needed because now the Express object comes from there: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/8fb0e959c2c7529b5fa4793a44b41b797ae671b9/types/express/index.d.ts#L19

Basically, use the following:

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

答案 10 :(得分:1)

对我有用的简单解决方案是创建一个扩展快速请求的新自定义界面。 此接口应包含所有自定义 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){}

答案 11 :(得分:1)

在具有节点12.19.0和Express 4的Mac上,使用Passport进行身份验证,我需要扩展我的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;
}```

答案 12 :(得分:1)

这是使用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",
    ]        

答案 13 :(得分:1)

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

  1. 请确保您的类型源包含在tsconfig文件中(这可能是一个全新的话题)
  2. 在类型目录中添加一个新目录,并将其命名为您要扩展或为其创建类型的包。在这种情况下,您将创建一个名称为express
  3. 的目录。
  4. express目录中创建一个文件并将其命名为index.d.ts(必须完全一样)
  5. 最后要扩展类型,您只需要输入如下代码:
declare module 'express' {
    export interface Request {
        property?: string;
    }
}

答案 14 :(得分:1)

在2020年5月下旬尝试扩展ExpressJS的请求时,对我有用的是要帮助任何正在寻找其他尝试的人。要使此功能生效,我必须尝试了十几种方法:

  • 在tsconfig.json的“ typeRoots”中翻转每个人的建议顺序(如果您在tsconfig中具有rootDir设置,例如“ ./src”,请不要忘记删除src路径)。示例:
VIPER
  • 自定义扩展的示例('./your-custom-types-dir/express/index.d.ts“)。根据我的经验,我不得不使用内联导入和默认导出来将类用作类型,因此也显示了:
"typeRoots": [
      "./node_modules/@types",
      "./your-custom-types-dir"
]
  • 更新您的nodemon.json文件,以将“ --files”命令添加到ts-node,例如:
declare global {
  namespace Express {
    interface Request {
      customBasicProperty: string,
      customClassProperty: import("../path/to/CustomClass").default;
    }
  }
}

答案 15 :(得分:1)

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

我也一直在担心扩展 request 对象的问题,我在堆栈溢出中遵循了很多答案,并最终遵循了以下提到的策略。

我在以下目录中声明了 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代码没有抱怨并且能够找到类型定义,这暗示ts-node编译器出了问题。

script中更新开始package.json对我来说是固定的。

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

--files自变量在确定自定义类型定义方面起着关键作用。

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

答案 16 :(得分:1)

尝试了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 = 
});

答案 17 :(得分:1)

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

现在,我们还可以向该对象添加任何其他所需的属性。

答案 18 :(得分:1)

虽然这是一个非常老的问题,但最近我偶然发现了这个问题。可以接受的答案很好,但是我需要在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 {
    ...
}

答案 19 :(得分:0)

这实际上不是直接回答问题,但我提供了一种替代方法。我在同一个问题上苦苦挣扎,并尝试了此页面上几乎所有扩展接口的解决方案,但没有一个起作用。

这让我停下了思考:“ 为什么我实际上在修改请求对象?”。

Express开发人员似乎已经认为用户可能想要添加自己的属性。这就是为什么有一个locals对象的原因。问题在于,它不在request对象中,而在response对象中。

response.locals对象可以包含您可能希望拥有的任何自定义属性,它们封装在请求-响应周期中,因此不会暴露给来自不同用户的其他请求。

需要存储userId吗?只需设置response.locals.userId = '123'。无需为输入而烦恼。

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

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

答案 20 :(得分:0)

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

答案 21 :(得分:0)

这对我有用:

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,
            ///....
        });
    }));

答案 22 :(得分:-8)

为什么我们可以这样做而为什么需要做很多麻烦,就像上面接受的答案一样?

我们可以将其附加到请求标头

,而不是将我们的媒体资源附加到请求
   req.headers[property] = "hello"