5
votes

Impossible d'étendre la requête express dans TypeScript

Cette question a été posée plusieurs fois, mais aucune des réponses qui ont été données n'a fonctionné pour moi. J'essaie d'étendre l'objet Request Express pour inclure une propriété pour stocker un objet User . J'ai créé un fichier de déclaration, express.d.ts , et l'ai placé dans le même répertoire que mon tsconfig.json:

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

Ensuite, j'essaye de lui faire une affectation dans secured-api.ts:

{
    "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'. */
    }
}

J'obtiens l'erreur suivante:

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;
}

Ma classe User est:

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

38                 req.user = user;
                       ~~~~

Mon tsconfig.json est:

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

Ma structure de répertoire est:

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

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

Qu'est-ce que je fais de mal ici?


0 commentaires

4 Réponses :


12
votes

Le problème est que vous n’augmentez pas l’espace de noms global Express défini par express vous créez un nouvel espace de noms dans votre module (le fichier devient un module une fois que vous utilisez une import ).

La solution est de déclarer l'espace de noms dans global

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

Ou de ne pas utiliser le module syntaxe d'importation, référencez simplement le type:

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

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


3 commentaires

Merci beaucoup! J'ai mis le bloc declare global dans express.d.ts , et j'ai dû supprimer le declare de declare namespace Express pour le faire fonctionner, mais cela fonctionne maintenant.


@ user3814613 wops a oublié la déclaration supplémentaire, supprimée. Désolé


Gentil monsieur. Existe-t-il un moyen d'automatiser cela avec tsc? peut-être quand j'ai express_ext.d.ts dans mon dossier src , le contenu de express_ext.d.ts automatiquement à la déclaration globale dans index.d.ts ?



3
votes

J'ai eu le même problème ...

Vous ne pouvez pas simplement utiliser une requête étendue? comme:

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

Sur une autre note, si vous utilisez des rappels asynchrones avec express, assurez-vous d'utiliser des wrappers express-async ou assurez-vous de savoir exactement ce que vous faites. Je recommande: awaitjs


4 commentaires

Je ne suis pas sûr que cela fonctionnera avec strictFunctionTypes si user n'est pas facultatif, car le rappel attend un paramètre req avec plus de clés que post dit qu'il peut fournir


Oui, cela fonctionnerait aussi, mais l'autre réponse a une solution plus facile à mettre en œuvre. @ TitianCernicova-Dragomir Je ne peux le faire fonctionner qu'en rendant user facultatif. Si ce n'est pas facultatif, le compilateur se plaint que la fonction ne peut pas être assignée au type PathParams .


J'utilise actuellement cette solution pour injecter un utilisateur dans la demande à des fins d'authentification et de contrôle d'accès. (L'utilisateur est injecté dans un middleware de passeport). L'utilisateur doit cependant être facultatif ... donc ce n'est pas parfait. Je n'aime pas l'idée d'étendre un autre espace de noms, donc ... je pense que c'est une question de préférence.


Pourquoi le champ user est-il facultatif dans RequestWithUser? J'ai remarqué que cela ne fonctionnera pas si ce n'est pas facultatif



-1
votes

0

La fusion de déclarations signifie que le compilateur fusionne deux déclarations distinctes déclarées avec le même nom en une seule définition.

Voir l'exemple ci-dessous dans lequel les déclarations d'interface sont fusionnées en typographie

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

interface Boy {
mark: number;
}

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

prolonger la demande réponse


0 commentaires

0
votes

J'ai essayé beaucoup de solutions mais aucune d'elles n'a fonctionné.

mais cela peut être aussi simple et fonctionne parfaitement. et bien sûr, vous pouvez enregistrer AuthenticatedRequest n'importe où ailleurs.

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;
  }
}

UPDATE:

J'ai donc trouvé quelque chose d'intéressant d'ici: Importer la classe dans le fichier de définition (* d.ts) . p>

déclare que l'espace de noms Express doit être la première ligne pour le dactylographie le compte comme global

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

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


0 commentaires