无法在TypeScript中扩展Express请求(Request)

14

这个问题已经有几次被问到了,但是没有一个给出的答案对我有效。我正在尝试扩展Express Request对象,以包括一个属性来存储一个User对象。我创建了一个声明文件 express.d.ts,并将其放置在与我的 tsconfig.json相同的目录中:

import { User } from "./src/models/user";

declare namespace Express {
    export interface Request {
        user: User;
    }
}

然后我尝试在 secured-api.ts 中对其进行赋值:

import express from 'express';
import { userService } from '../services/user';

router.use(async (req, res, next) => {
    try {
        const user = await userService.findByUsername(payload.username);

        // do stuff to user...

        req.user = user;
        next();
    } catch(err) {
        // handle error
    }
});

我遇到了如下错误:

src/routes/secured-api.ts:38:21 - error TS2339: Property 'user' does not exist on type 'Request'.

38                 req.user = user;
                       ~~~~

我的User类是:

import { Model, RelationMappings } from 'objection';

export class User extends Model {

    public static tableName = 'User';
    public static idColumn = 'username';

    public static jsonSchema = {
        type: 'object',
        required: ['fname', 'lname', 'username', 'email', 'password'],

        properties: {
            fname: { type: 'string', minLength: 1, maxLength: 30 },
            lname: { type: 'string', minLength: 1, maxLength: 30 },
            username: { type: 'string', minLength: 1, maxLength: 20 },
            email: { type: 'string', minLength: 1, maxLength: 320 },
            password: { type: 'string', minLength: 1, maxLength: 128 },
        }
    };

    public static modelPaths = [__dirname];

    public static relationMappings: RelationMappings = {

    };

    public fname!: string;
    public lname!: string;
    public username!: string;
    public email!: string;
    public password!: string;
}

我的tsconfig.json文件是:

{
    "compilerOptions": {
      "target": "es6",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
      "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
      "lib": ["es2015"],                             /* Specify library files to be included in the compilation. */
      "outDir": "./build",                      /* Redirect output structure to the directory. */
      "strict": true,                           /* Enable all strict type-checking options. */
      "esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    }
}

我的目录结构是:

backend/
    package.json
    tsconfig.json
    express.d.ts
    src/
        models/
            user.ts
        routes/
            secured-api.ts

我在这里做错了什么?

4个回答

26

问题在于您没有增强由express定义的Express全局命名空间,而是在您的模块中创建了一个新的命名空间(一旦使用import,文件就变成了模块)。

解决方法是在global中声明该命名空间。

import { User } from "./src/models/user";

declare global {
    namespace Express {
        export interface Request {
            user: User;
        }
    }
}

或者不使用模块导入语法,只引用类型:

declare namespace Express {
    export interface Request {
        user: import("./src/models/user").User;
    }
}

1
非常感谢!我将“declare global”块放在“express.d.ts”中,不得不从“declare namespace Express”中删除“declare”,才能使其起作用,但现在它可以工作了。 - user3814613
1
@user3814613 噢,忘记在那里加上额外的声明了,已经删掉了。抱歉。 - Titian Cernicova-Dragomir
有位亲切的先生,是否有办法通过 tsc 自动化这个过程呢?也许当我在我的“src”文件夹中有“express_ext.d.ts”时,能否自动将“express_ext.d.ts”的内容声明为全局声明在“index.d.ts”中? - Fandi Susanto
1
namespace Express 对我似乎不起作用。我不得不使用 declare module 'express' 代替。你知道为什么会出现这种情况吗? - DollarAkshay
在创建了 express/ 目录下的 index.d.ts 文件后,declare namespace Express 的解决方案得以实现。 - gtmsingh
这会触发 typescript-eslint/no-namespace 警告,根据 eslint 推荐的设置。我会将其禁用,个人而言我不介意使用命名空间,但奇怪的是这在 eslint 推荐中被严重反对。 - JHH

7
我曾经遇到过同样的问题... 难道你不能使用扩展请求吗? 像这样:
interface RequestWithUser extends Request {
    user?: User;
}
router.post('something',(req: RequestWithUser, res: Response, next)=>{
   const user = req.user;
   ...
}

在另一个方面,如果您正在使用与express结合的异步回调,请确保使用express-async包装器或确保您对此非常了解。我推荐使用:awaitjs

1
如果user不是可选的,那么使用strictFunctionTypes可能无法正常工作,因为回调函数期望一个具有比post提供的更多键的req参数。 - Titian Cernicova-Dragomir
是的,那也可以,但另一个答案有一个更容易实现的解决方案。@TitianCernicova-Dragomir我只能通过将“user”变成可选项来使其工作。如果不是可选的,编译器就会抱怨该函数不能分配给类型“PathParams”。 - user3814613
1
我目前使用这种方案将用户注入请求以进行身份验证和访问控制。 (用户被注入到护照中间件中)。 但是用户需要是可选的... 所以它并不完美。 我不喜欢扩展另一个命名空间的想法,因此...我认为这是一个偏好问题。 - Nuno Sousa
为什么RequestWithUser中的“user”字段是可选的?我注意到如果它不是可选的,这个程序就无法工作。 - JAM
我选择使用它。至少在我看来,它看起来很干净。 - 6991httam

3

我尝试了很多解决方案,但都没有奏效。

但事实上也可以这么简单并且完美地工作。 当然,你也可以将AuthenticatedRequest保存在其他任何地方。

import { IUser } from './src/_models/user.model';

export interface AuthenticatedRequest extends Request {
  user: IUser;
}
static async getTicket(req: AuthenticatedRequest, res: Response){}

更新:

我从这里发现了一些有趣的东西: 在定义文件(*d.ts)中导入类

声明命名空间Express应该是第一行,以便TypeScript将其视为全局。

declare namespace Express {

  interface Request {
    user: import('./src/modules/models/user.model').IUser;
    uploadedImage: { key: string; type: string };
    group: import('./src/modules/models/group.model').IGroup;
  }
}

-2

0

声明合并是指编译器将使用相同名称声明的两个独立声明合并为单个定义。

请参见以下示例,其中 TypeScript 中的接口声明已合并:

interface Boy {
height: number;
weight: number;
}

interface Boy {
mark: number;
}

let boy: Boy = {height: 6 , weight: 50, mark: 50}; 

扩展请求响应


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