【问题标题】:Unable to extend Express Request in TypeScript无法在 TypeScript 中扩展 Express 请求
【发布时间】:2019-05-30 12:29:11
【问题描述】:

这个问题已经被问了好几次了,但是给出的答案都没有对我有用。我正在尝试扩展 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

我在这里做错了什么?

【问题讨论】:

    标签: node.js typescript express


    【解决方案1】:

    问题在于您没有扩充由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;
        }
    }
    

    【讨论】:

    • 非常感谢!我将declare global 块放在express.d.ts 中,我必须从declare namespace Express 中删除declare 才能让它工作,但它现在可以工作了。
    • @user3814613 wops 忘记了其中的额外声明,将其删除。对不起
    • 好心的先生。有什么办法可以用 tsc 自动完成吗?也许当我的src 文件夹中有express_ext.d.ts 时,express_ext.d.ts 的内容会自动在index.d.ts 中进行全局声明?
    • namespace Express 似乎对我不起作用。我不得不改用declare module 'express'。知道为什么会这样吗?
    • declare namespace Express 解决方案在我在express/ 目录中创建index.d.ts 文件后起作用。
    【解决方案2】:

    我也遇到了同样的问题...

    您不能只使用扩展请求吗? 喜欢:

    interface RequestWithUser extends Request {
        user?: User;
    }
    router.post('something',(req: RequestWithUser, res: Response, next)=>{
       const user = req.user;
       ...
    }
    

    另一方面,如果您将异步回调与 express 一起使用,请确保使用 express-async 包装器或确保您确切知道自己在做什么。我推荐:awaitjs

    【讨论】:

    • 如果user 不是可选的,则不确定是否可以与strictFunctionTypes 一起使用,因为回调期望req 参数的键数比post 说它可以提供的要多
    • 是的,这也可以,但另一个答案有一个更容易实施的解决方案。 @TitianCernicova-Dragomir 我只能通过将 user 设为可选来使其工作。如果它不是可选的,编译器会抱怨该函数不能分配给类型PathParams
    • 我目前使用此解决方案将用户注入请求以进行身份​​验证和访问控制。 (用户被注入护照中间件)。用户需要是可选的......所以它并不完美。我不喜欢扩展另一个命名空间的想法,所以......我认为这是一个偏好问题。
    • 为什么 user 字段在 RequestWithUser 中是可选的?我注意到如果它不是可选的,这将不起作用
    • 我同意。至少对我来说,它看起来最干净。
    【解决方案3】:

    0

    声明合并意味着编译器将两个独立的同名声明合并到一个定义中。

    见下面的例子,其中接口声明被合并到打字稿中

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

    extend request response

    【讨论】:

      【解决方案4】:

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

      但它也可以如此简单并且完美运行。 当然,您可以将 AuthenticatedRequest 保存在其他任何地方。

      import { IUser } from './src/_models/user.model';
      
      export interface AuthenticatedRequest extends Request {
        user: IUser;
      }
      static async getTicket(req: AuthenticatedRequest, res: Response){}
      

      更新:

      所以我从这里发现了一些有趣的东西: Import class in definition file (*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;
        }
      }
      

      【讨论】:

        猜你喜欢
        • 2021-01-07
        • 2021-01-12
        • 2017-11-23
        • 2021-06-05
        • 1970-01-01
        • 2017-02-26
        • 1970-01-01
        • 2018-12-07
        • 1970-01-01
        相关资源
        最近更新 更多