使用Typescript扩展Express Request对象

334

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

我正在寻找一种解决方案,使我可以编写类似于以下内容的代码(如果可能):

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

1
你应该能够通过扩展 express.d.ts 文件提供的请求接口来添加所需的字段。 - toskv
32个回答

2

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

2

对于我来说有效的简单解决方案是创建一个扩展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){}

这个问题涉及到向现有请求接口添加自定义属性,只能使用类型声明文件来完成。 - Abhishek Pankar
1
@AbhishekPankar 为什么你会说只有使用类型声明文件才能进行扩展呢?@AshiSultan 的实现看起来很好。不过我承认我无法让它正常工作。TypeScript 不喜欢在最终路由上应用此中间件时出现“没有匹配此调用的重载”错误。 - defraggled
@defraggled 我的意思是在不使用外部接口的情况下,类型声明是唯一的解决方案。 - Abhishek Pankar

2
我的解决方案,适用于typescript 4.8.4express 4.18.2:

COMMENT放在"declare global"中,像这样:

declare global {
  declare module 'express-serve-static-core' {
    interface Request {
      userId?: string;
    }
  }
}

文件结构:

/typeDeclarations/express/index.d.ts
/tsconfig.json

我还将我的声明路径添加到了tsconfig文件中,但是即使没有它一切也能正常工作。

  "typeRoots": [
    "./node_modules/@types",
    "./typeDeclarations"
  ],  

1

可能已经太晚了,但是无论如何,这就是我解决问题的方法:

  1. 确保在你的 tsconfig 文件中包含了你的类型源(这可能需要一个全新的线程)
  2. 在你的类型目录中添加一个新目录,并将其命名为你想要扩展或创建类型的包名称。在这个特定的情况下,你将创建一个名为 express 的目录
  3. express 目录中创建一个文件,并将其命名为 index.d.ts(必须完全一样)
  4. 最后,为了扩展类型,你只需要放置类似以下代码的代码:
declare module 'express' {
    export interface Request {
        property?: string;
    }
}

1
在使用node 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;
}```

1

最简单的方法是扩展您想要的类型并添加自己的属性

在 tsconfig.ts 中指定本地类型的根目录

{
  // compilerOptions:
  "typeRoots": ["node_modules/@types", "**/@types"],
}

现在在@types文件夹中创建任何.d.ts文件,您可以将@types放在根目录或任何地方。

@types/express.d.ts

declare namespace Express {
  interface Request {
    // add arbitrary keys to the request
    [key: string]: any;
  }
}

0
对于简单情况,我在外层中间件中使用 headers 属性,并稍后在内层中间件中获取它。
// outer middleware
req.headers["custom_id"] = "abcxyz123";

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

缺点:

  • 只能存储string。如果您想存储其他类型,如jsonnumber,可能需要稍后解析。
  • headers属性未经文档化。Express仅记录req.get()方法。因此,您必须使用与属性headers兼容的确切版本的Express。

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

替代“声明合并”的方法

以下是可能的选项:

  • 创建函数,用于获取请求的上下文属性/功能(A)
  • 创建单个函数,从请求中获取完全自定义的上下文(B)。 专门的上下文函数选项类似于
async function getRequestUser(req:Request):Promise<ICustomUser> {
   let currentUser:ICustomUser = req[UserSymbolProp] || null as ICustomUser;
   // if user not set already, try load it, if no such user but header present, throw error?
   return currentUser;
} 
app.get('/api/get/something', async(req, res, next) => {
   try {
      let user = await getRequestUser(req);
      //do something else
   } catch(err) {
     next(err);
   }
});

另一个选项相当类似,但你可以创建一个单一的函数,在代码中返回你所需的所有自定义上下文。
function getAPIContext(req:Request):IAPIContext {
   let ctx = req[APICtxSymbol] || null as IApiContext;
   if (!ctx) {
     ctx = prepareAPIContext(req);
     req[APICtxSymbol] = ctx;
   }
   return ctx;
}

app.get('/api/to/get/something', async(req, res, next) => {
   try {
      let ctx = getAPIContext(req);
      ///use context somehow
      let reply = await doSomething(ctx);
      res.json(reply);
   } catch(err) {
      next(err);
   }
}

第二种方法更好,因为您可以创建使用测试特殊上下文实现的单元测试,并直接在 doSomething 上进行单元测试(当然要导出该代码)。 第二个结构可以通过类似 wrapHandler 的函数重复使用,该函数接受真实的处理函数。

function wrapHandler<T>(handler: (req:IAPIContext) => Promise<T>|T) => (req:Request, res:Response, next:NextFunction) => Promise<void>|void;

你的 AsyncRequestHandlerAsyncHandlerWrapper 类型有什么作用吗?它们被声明了但在你的示例中没有被使用。 - devklick
这些类型是API的一部分,我在使用扩展请求时使用了asyncHandlers,因此我将这些类型作为使用上下文的一部分进行了复制,以确保req和res被视为express Request和Response类型,而不是DOM fetch Request和Response类型。如果您使用原始的express,请在处理程序函数参数声明中明确指定reqres的类型,以确保接口合并按预期工作。 - Kote Isaev
1
命名空间 e 和命名空间 Express 之间有什么区别?因为在 DefinitelyTyped 中,似乎 @types/express 使用了 e,而 @types/express-serve-static-core 使用了 Express,但我无法确定具体的区别是什么。 - damd

0
dep : "@types/express": "^4.17.17" "typescript": "^5.1.6" 我的目标是将__platform添加为req对象的属性,并定义req.body和req.query的类型。以下是我想要使用的具体代码。
import Request from 'express';
interface QueryProps{
  username: string;
}
interface BodyProps{
  witdh: number;
  height: number;
}
type IRequest = Request<object,object,BodyProps,QueryProps>
function testHandler(req:IRequest,res,next){
  req.__platform // no error
  req.query.username // no error 
  req.body.with // no error 
  req.body.height // no error
}

以下是适用于我的类型声明。
// index.d.ts
import { Request as ExpressRequest } from 'express';
declare module 'express' {
  interface Request extends ExpressRequest {
    __platform: string;
  }
}
declare module 'express-serve-static-core' {
  interface Request {
    __platform: string;
  }
}

-1

我已经将响应类型更改为包括 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){
        /* ... */
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接