簡體   English   中英

使用 Typescript 擴展快速請求 object

[英]Extend Express Request object using Typescript

我正在嘗試添加一個屬性以使用 typescript 從中間件表達請求 object。 但是我不知道如何向 object 添加額外的屬性。 如果可能的話,我寧願不使用括號表示法。

我正在尋找一種解決方案,可以讓我寫出類似的東西(如果可能的話):

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

您想創建一個自定義定義,並使用 Typescript 中名為Declaration Merging的功能。 這是常用的,例如在method-override中。

創建一個文件custom.d.ts並確保將其包含在您的tsconfig.jsonfiles -section(如果有)中。 內容可以如下所示:

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 還介紹了擴展全局命名空間。

對於較新版本的 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
  }
}

在嘗試了 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 = 
});

接受的答案(與其他人一樣)對我不起作用,但

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

做過。 希望這會幫助某人。

提供的解決方案都不適合我。 我最終只是簡單地擴展了 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 = '';
}

替代解決方案

這實際上並沒有直接回答這個問題,但我提供了一個替代方案。 我在同一個問題上苦苦掙扎,並嘗試了此頁面上的幾乎所有接口擴展解決方案,但都沒有奏效。

這讓我停下來思考: 為什么我實際上要修改請求對象?

使用response.locals

Express 開發人員似乎認為用戶可能想要添加自己的屬性。 這就是為什么有一個locals對象。 問題是,它不在request中,而是在response對象中。

response.locals對象可以包含您可能想要的任何自定義屬性,封裝在請求-響應周期中,因此不會暴露給來自不同用戶的其他請求。

需要存儲用戶 ID? 只需設置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
  }
);

所有這些反應似乎都以某種方式錯誤或過時。

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

干杯。

在 2021 年,這一項正在發揮作用:

使用 express 4.17.1, https://stackoverflow.com/a/55718334/9321986 和 https://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"]
    }
}

雖然這是一個非常古老的問題,但我最近偶然發現了這個問題。接受的答案工作正常,但我需要向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 {
    ...
}

這就是在使用 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",
    ]        

在 TypeScript 中,接口是開放式的。 這意味着您可以通過重新定義它們從任何地方向它們添加屬性。

考慮到您正在使用這個express.d.ts文件,您應該能夠重新定義Request接口以添加額外的字段。

interface Request {
  property: string;
}

然后在你的中間件函數中, req參數也應該有這個屬性。 您應該能夠在不更改代碼的情況下使用它。

如果您正在尋找與 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

這個答案將對那些依賴 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編譯器有問題。

更新package.json中的啟動script為我修復了它。

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

--files參數在這里發揮關鍵作用,確定自定義類型定義。

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

一種可能的解決方案是使用“雙重轉換到任何”

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

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

也許這個問題已經回答了,但我想分享一點,現在有時像其他答案這樣的界面可能有點過於嚴格,但我們實際上可以維護所需的屬性,然后通過創建一個添加任何額外的屬性來添加鍵類型為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();
});

所以現在,我們還可以向這個對象添加我們想要的任何其他屬性。

如果您嘗試了所有答案但仍然無法正常工作,這里有一個簡單的 hack

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

這會將req對象轉換為any ,因此您可以添加所需的任何屬性。 請記住,這樣做會失去req對象的類型安全性。

我有同樣的問題並像這樣解決它:

// /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

我使用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}` });
});

我通過創建一個新類型而不全局擴展 Request 類型解決了這個問題。

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

您必須將擴展屬性與optional(?)運算符一起使用,以免出現“沒有重載匹配此調用”錯誤。

包版本:

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

在具有節點 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;
}```

對我有用的簡單解決方案是創建一個擴展快速請求的新自定義接口。 這個接口應該包含你所有的自定義 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){}

這個答案可能已經很晚了,但無論如何我是這樣解決的:

  1. 確保您的tsconfig文件中包含您的類型源(這可能是一個全新的線程)
  2. 在您的類型目錄中添加一個新目錄並將其命名為您要擴展或為其創建類型的包。 在這種特定情況下,您將創建一個名為express的目錄
  3. express目錄中創建一個文件並將其命名為index.d.ts (必須完全一樣)
  4. 最后,要擴展類型,您只需要輸入如下代碼:
declare module 'express' {
    export interface Request {
        property?: string;
    }
}

這對我有用:

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

對於簡單的情況,我在外部中間件中使用headers屬性,稍后在內部中間件中獲取它。

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

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

缺點:

  • 只能存儲string 如果要存儲其他類型,例如jsonnumber ,則可能需要稍后對其進行解析。
  • headers屬性沒有記錄。 Express 僅記錄req.get()方法。 因此,您必須使用與 property headers一起使用的 Express 的確切版本。

我已將響應類型更改為包括 ApiResponse(我的自定義響應 obj) 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'})

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

只需使用表達促進的語言而不是不安全的突變。

只需將屬性添加到 req.params 對象。

req.params.foo = "bar"

為什么我們需要像上面接受的答案那樣麻煩,當我們可以這樣做時

而不是我們的財產附加到請求時,我們可以將其連接到請求頭

   req.headers[property] = "hello"

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM