Étendre l'objet de requête express à l'aide de Typescript

129

J'essaie d'ajouter une propriété pour exprimer un objet de requête à partir d'un middleware en utilisant du typographie. Cependant, je ne peux pas comprendre comment ajouter des propriétés supplémentaires à l'objet. Je préférerais ne pas utiliser la notation entre crochets si possible.

Je recherche une solution qui me permettrait d'écrire quelque chose de similaire (si possible):

app.use((req, res, next) => {
    req.property = setProperty(); 
    next();
});
Isak Ågren
la source
vous devriez pouvoir étendre l'interface de requête fournie par le fichier express.d.ts avec les champs souhaités.
toskv

Réponses:

148

Vous souhaitez créer une définition personnalisée et utiliser une fonctionnalité dans Typescript appelée Fusion de déclaration . Ceci est couramment utilisé, par exemple dans method-override.

Créez un fichier custom.d.tset assurez-vous de l'inclure dans votre section tsconfig.jsons, le filescas échéant. Le contenu peut se présenter comme suit:

declare namespace Express {
   export interface Request {
      tenant?: string
   }
}

Cela vous permettra, à tout moment de votre code, d'utiliser quelque chose comme ceci:

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)
})
maximilianvp
la source
2
Je viens de faire cela, mais je l'ai fait fonctionner sans ajouter mon fichier custom.d.ts à la section des fichiers de mon tsconfig.json, mais cela fonctionne toujours. Est-ce un comportement attendu?
Chaim Friedman
1
@ChaimFriedman Oui. La filessection restreint l'ensemble des fichiers inclus par TypeScript. Si vous ne spécifiez pas filesou include, alors tous *.d.tssont inclus par défaut, il n'est donc pas nécessaire d'y ajouter vos typages personnalisés.
interphx
9
Ne fonctionne pas pour moi: j'obtiens Property 'tenantn'existe pas sur le type 'Demande' `` Cela ne fait aucune différence si je l'inclus explicitement tsconfig.jsonou non. MISE À JOUR Avec declare globalcomme @basarat le souligne dans ses œuvres d'answear, mais je devais d' import {Request} from 'express'abord le faire .
Lion
5
FWIW, cette réponse est désormais obsolète . La réponse de JCM est la bonne façon d'augmenter l' Requestobjet dans expressjs (4.x au moins)
Eric Liprandi
3
Pour les recherches futures - un bon exemple que j'ai trouvé qui a fonctionné hors de la boîte: github.com/3mard/ts-node-example
jd291
80

Comme suggéré par les commentaires dans leindex.d.ts , vous déclarez simplement à l' Expressespace de noms global tous les nouveaux membres. Exemple:

declare global {
  namespace Express {
    interface Request {
      context: Context
    }
  }
}

Exemple complet:

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!'))

L'extension des espaces de noms globaux est plus abordée dans mon GitBook .

basarat
la source
Pourquoi le global est-il nécessaire dans la déclaration? Que se passe-t-il si ce n'est pas là?
Jason Kuhrt
Cela fonctionne avec les interfaces, mais au cas où quelqu'un aurait besoin de fusionner des types, notez que les types sont "fermés" et ne peuvent pas être fusionnés: github.com/Microsoft/TypeScript/issues
Peter W
M. @basarat, je vous dois des bières.
marcellsimon le
J'ai également dû ajouter à mon tsconfig.json: {"compilerOptions": {"typeRoots": ["./src/typings/", "./node_modules/@types"]}, "files": ["./ src / typings / express / index.d.ts "]}
marcellsimon
Aucune des solutions ci-dessus n'a fonctionné .. mais celle-ci a fait le travail lors de la première exécution .. merci beaucoup .. !!
Ritesh
56

Pour les versions plus récentes d'Express, vous devez augmenter le express-serve-static-coremodule.

Ceci est nécessaire car maintenant l'objet Express vient de là: https://github.com/DefinitelyTyped/DefinatelyTyped/blob/8fb0e959c2c7529b5fa4793a44b41b797ae671b9/types/express/index.d.ts#L19

En gros, utilisez ce qui suit:

declare module 'express-serve-static-core' {
  interface Request {
    myField?: string
  }
  interface Response {
    myField?: string
  }
}
JCM
la source
1
Cela a fonctionné pour moi, alors que l'extension de l'ancien 'express'module n'a pas fonctionné. Je vous remercie!
Ben Kreeger
4
J'avais du mal avec cela, pour que cela fonctionne, je devais également importer le module:import {Express} from "express-serve-static-core";
andre_b
1
@andre_b Merci pour l'indice. Je pense que l'instruction d'importation transforme le fichier en module, et c'est la partie qui est nécessaire. Je suis passé à l'utilisation export {}qui fonctionne également.
Danyal Aytekin
2
Assurez-vous que le fichier dans lequel ce code entre n'est pas appelé express.d.ts, sinon le compilateur essaiera de le fusionner avec les typages express, ce qui entraînera des erreurs.
Tom Spencer
3
Assurez-vous que vos types doivent être les premiers dans typeRoots! types / express / index.d.ts et tsconfig => "typeRoots": ["./src/types", "./node_modules/@types"]
kaya
31

La réponse acceptée (comme les autres) ne fonctionne pas pour moi mais

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

fait. J'espère que cela aidera quelqu'un.

max-lt
la source
2
Une méthode similaire est décrite dans ts docs sous "Module Augmentation". C'est génial si vous ne voulez pas utiliser de *.d.tsfichiers et simplement stocker vos types dans des *.tsfichiers normaux .
im.pankratov
3
c'est la seule chose qui a fonctionné pour moi aussi, toutes les autres réponses semblent devoir être dans des fichiers .d.ts
parlement
Cela fonctionne aussi pour moi, à condition que je mette mon custom-declarations.d.tsfichier à la racine du projet TypeScript.
focorner
J'ai étendu le type d'origine pour le conserver: import { Request as IRequest } from 'express/index';et interface Request extends IRequest. Il fallait également ajouter le type Racine
Ben Creasy
17

Après avoir essayé environ 8 réponses et sans succès. J'ai finalement réussi à le faire fonctionner avec jd291 commentaire de pointage à 3mards repo .

Créez un fichier dans la base appelé types/express/index.d.ts. Et écrivez-y:

declare namespace Express {
    interface Request {
        yourProperty: <YourType>;
    }
}

et incluez-le tsconfig.jsonavec:

{
    "compilerOptions": {
        "typeRoots": ["./types"]
    }
}

Ensuite, yourPropertydevrait être accessible sous chaque demande:

import express from 'express';

const app = express();

app.get('*', (req, res) => {
    req.yourProperty = 
});
kaleidawave
la source
14

Aucune des solutions proposées n'a fonctionné pour moi. J'ai fini par étendre simplement l'interface de requête:

import {Request} from 'express';

export interface RequestCustom extends Request
{
    property: string;
}

Alors pour l'utiliser:

import {NextFunction, Response} from 'express';
import {RequestCustom} from 'RequestCustom';

someMiddleware(req: RequestCustom, res: Response, next: NextFunction): void
{
    req.property = '';
}

Edit : Les versions récentes de TypeScript se plaignent de cela. Au lieu de cela, j'ai dû faire:

someMiddleware(expressRequest: Request, res: Response, next: NextFunction): void
{
    const req = expressRequest as RequestCustom;
    req.property = '';
}
Tom Mettam
la source
1
cela fonctionnera, mais assez verbeux si vous avez des centaines de fonctions middleware, amirite
Alexander Mills
1
@ user2473015 Oui, les versions récentes de Typescript ont cassé cela. Voir ma réponse mise à jour.
Tom Mettam
8

Dans TypeScript, les interfaces sont ouvertes. Cela signifie que vous pouvez leur ajouter des propriétés de n'importe où en les redéfinissant simplement.

Étant donné que vous utilisez ce fichier express.d.ts , vous devriez pouvoir redéfinir l' interface de demande pour ajouter le champ supplémentaire.

interface Request {
  property: string;
}

Ensuite, dans votre fonction middleware, le paramètre req doit également avoir cette propriété. Vous devriez pouvoir l'utiliser sans aucune modification de votre code.

toskv
la source
1
Comment «partagez-vous» ces informations tout au long de votre code? Si je définir une propriété dans la demande, par exemple Request.user = {};dans la app.tsfaçon dont ne userController.tssait à ce sujet?
Nepoxx
2
@Nepoxx si vous redéfinissez une interface, le compilateur fusionnera les propriétés et les rendra visibles partout, c'est pourquoi. Idéalement, vous feriez la redéfinition dans un fichier .d.ts. :)
toskv
Cela semble fonctionner, mais si j'utilise le type express.Handler(au lieu de spécifier manuellement (req: express.Request, res: express.Response, next: express.NextFunction) => any)), il ne semble pas faire référence au même Requestcar il se plaint que ma propriété n'existe pas.
Nepoxx
Je ne m'y attendais pas, à moins qu'express.Handler n'étende l'interface Request. le fait-il?
toskv
2
Je peux faire fonctionner ça si j'utilise declare module "express"mais pas si j'utilise declare namespace Express. Je préfère utiliser la syntaxe de l'espace de noms, mais cela ne fonctionne tout simplement pas pour moi.
WillyC
5

Bien qu'il s'agisse d'une question très ancienne, je suis tombé sur ce problème récemment.La réponse acceptée fonctionne bien mais je devais ajouter une interface personnalisée à Request- une interface que j'avais utilisée dans mon code et qui ne fonctionnait pas si bien avec l'accepté répondre. Logiquement, j'ai essayé ceci:

import ITenant from "../interfaces/ITenant";

declare namespace Express {
    export interface Request {
        tenant?: ITenant;
    }
}

Mais cela n'a pas fonctionné car Typescript traite les .d.tsfichiers comme des importations globales et lorsqu'ils contiennent des importations, ils sont traités comme des modules normaux. C'est pourquoi le code ci-dessus ne fonctionne pas sur un paramètre dactylographié standard.

Voici ce que j'ai fini par faire

// typings/common.d.ts

declare namespace Express {
    export interface Request {
        tenant?: import("../interfaces/ITenant").default;
    }
}
// interfaces/ITenant.ts

export interface ITenant {
    ...
}
16 Ko
la source
Cela fonctionne pour mon fichier principal, mais pas dans mes fichiers de routage ou mes contrôleurs, je n'obtiens pas de peluchage, mais quand j'essaye de compiler, il dit "La propriété 'utilisateur' n'existe pas sur le type 'Demande'." (J'utilise user au lieu de tenant), mais si j'ajoute // @ ts-ignore au-dessus d'eux, alors cela fonctionne (bien que ce soit une façon idiote de résoudre le problème, bien sûr. Avez-vous des idées sur les raisons pour lesquelles cela peut ne pas être travaille pour mes autres fichiers?
Logan
C'est une chose très étrange @Logan. Pouvez - vous partager votre .d.ts, tsconfig.jsonet l'instance d'utilisation? De plus, quelle version de typographie utilisez-vous car cette importation dans les modules globaux n'est prise en charge qu'à partir de TS 2.9? Cela pourrait mieux aider.
16kb le
J'ai téléchargé des données ici, pastebin.com/0npmR1Zr Je ne sais pas pourquoi la mise en surbrillance est tout gâchée Cela provient du fichier principal prnt.sc/n6xsyl Ceci provient d'un autre fichier prnt.sc/n6xtp0 Clairement une partie de il comprend ce qui se passe, mais pas le compilateur. J'utilise la version 3.2.2 de dactylographié
Logan
1
Étonnamment, ... "include": [ "src/**/*" ] ...fonctionne pour moi mais "include": ["./src/", "./src/Types/*.d.ts"],ne le fait pas. Je ne suis pas encore allé en profondeur pour essayer de comprendre cela
16kb
L'importation d'interface à l'aide d'importations dynamiques fonctionne pour moi. Merci
Roman Mahotskyi
3

Peut-être que ce problème a été résolu, mais je veux partager juste un peu, maintenant parfois une interface comme d'autres réponses peut être un peu trop restrictive, mais nous pouvons en fait maintenir les propriétés requises, puis ajouter des propriétés supplémentaires à ajouter en créant un clé avec un type de stringavec type valeur deany

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

Alors maintenant, nous pouvons également ajouter toute propriété supplémentaire que nous voulons à cet objet.

Eka putra
la source
3

Toutes ces réponses semblent erronées ou dépassées d'une manière ou d'une autre.

Cela a fonctionné pour moi en mai 2020:

dans ${PROJECT_ROOT}/@types/express/index.d.ts:

import * as express from "express"

declare global {
    namespace Express {
        interface Request {
            my_custom_property: TheCustomType
        }
    }
}

dans tsconfig.json, ajoutez / fusionnez la propriété de telle sorte que:

"typeRoots": [ "@types" ]

À votre santé.

Will Brickner
la source
Fonctionne avec Webpack + Docker, l'importation * peut être remplacée par l'exportation {};
Dooomel
2

Si vous recherchez une solution qui fonctionne avec express4, la voici:

@ types / express / index.d.ts: -------- doit être /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",
    ]
  }
}

Réf de https://github.com/TypeStrong/ts-node/issues/715#issuecomment-526757308

Chandara Chea
la source
1

Une solution possible consiste à utiliser la "double coulée vers tout"

1- définir une interface avec votre propriété

export interface MyRequest extends http.IncomingMessage {
     myProperty: string
}

2- double fonte

app.use((req: http.IncomingMessage, res: http.ServerResponse, next: (err?: Error) => void) => {
    const myReq: MyRequest = req as any as MyRequest
    myReq.myProperty = setProperty()
    next()
})

Les avantages de la double coulée sont les suivants:

  • les typages sont disponibles
  • il ne pollue pas les définitions existantes mais les étend, évitant toute confusion
  • puisque le casting est explicite, il compile les amendes avec le -noImplicitanydrapeau

Alternativement, il y a l'itinéraire rapide (non typé):

 req['myProperty'] = setProperty()

(ne modifiez pas les fichiers de définition existants avec vos propres propriétés - cela ne peut pas être maintenu. Si les définitions sont incorrectes, ouvrez une demande d'extraction)

ÉDITER

Voir le commentaire ci-dessous, le casting simple fonctionne dans ce cas req as MyRequest

Bruno Grieder
la source
@akshay Dans ce cas, oui, car MyRequestétend le http.IncomingMessage. Ce n'était pas le cas, le double casting via anyserait la seule alternative
Bruno Grieder
Il est recommandé de lancer un cast vers inconnu au lieu de tout.
dev
0

Cette réponse sera bénéfique pour ceux qui comptent sur le package npm ts-node.

Je luttais également avec le même souci d'étendre l' objet de requête , j'ai suivi beaucoup de réponses dans le stack-overflow et j'ai fini par suivre la stratégie mentionnée ci-dessous.

J'ai déclaré la saisie étendue pour express dans le répertoire suivant.${PROJECT_ROOT}/api/@types/express/index.d.ts

declare namespace Express {
  interface Request {
    decoded?: any;
  }
}

puis mettre tsconfig.jsonà jour mon à quelque chose comme ça.

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

même après avoir effectué les étapes ci-dessus, le studio visuel a cessé de se plaindre, mais malheureusement, le ts-nodecompilateur avait encore l'habitude de lancer.

 Property 'decoded' does not exist on type 'Request'.

Apparemment, le ts-noden'a pas pu localiser les définitions de type étendues pour l' objet de requête .

Finalement, après avoir passé des heures, car je savais que le code VS ne se plaignait pas et était capable de localiser les définitions de frappe, ce qui implique que quelque chose ne va pas avec le ts-nodecomplier.

Mise à jour de départ scripten package.jsonfixe pour moi.

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

les --filesarguments jouent ici un rôle clé pour déterminer les définitions de type personnalisées.

Pour plus d'informations, veuillez visiter: https://github.com/TypeStrong/ts-node#help-my-types-are-missing

Divyanshu Rawat
la source
0

Aider tous ceux qui recherchent simplement quelque chose d'autre à essayer, voici ce qui a fonctionné pour moi fin mai 2020 en essayant d'étendre la demande d'ExpressJS. J'ai dû essayer plus d'une douzaine de choses avant que cela fonctionne:

  • Inversez l'ordre de ce que tout le monde recommande dans le "typeRoots" de votre tsconfig.json (et n'oubliez pas de supprimer le chemin src si vous avez un paramètre rootDir dans tsconfig tel que "./src"). Exemple:
"typeRoots": [
      "./node_modules/@types",
      "./your-custom-types-dir"
]
  • Exemple d'extension personnalisée ('./your-custom-types-dir/express/index.d.ts "). J'ai dû utiliser l'importation en ligne et les exportations par défaut pour utiliser des classes comme type dans mon expérience, ce qui est également montré:
declare global {
  namespace Express {
    interface Request {
      customBasicProperty: string,
      customClassProperty: import("../path/to/CustomClass").default;
    }
  }
}
  • Mettez à jour votre fichier nodemon.json pour ajouter la commande "--files" à ts-node, exemple:
{
  "restartable": "rs",
  "ignore": [".git", "node_modules/**/node_modules"],
  "verbose": true,
  "exec": "ts-node --files",
  "watch": ["src/"],
  "env": {
    "NODE_ENV": "development"
  },
  "ext": "js,json,ts"
}
Si vrai
la source
0

Il est peut-être déjà assez tard pour cette réponse, mais de toute façon, voici comment j'ai résolu:

  1. Assurez-vous que la source de vos types est incluse dans votre tsconfigfichier (cela pourrait être un tout nouveau fil)
  2. Dans votre répertoire de types, ajoutez un nouveau répertoire et nommez-le comme package pour lequel vous souhaitez étendre ou créer des types. Dans ce cas précis, vous allez créer un répertoire avec le nomexpress
  3. Dans le expressrépertoire, créez un fichier et nommez-le index.d.ts(DOIT ÊTRE EXACTEMENT COMME CELA)
  4. Enfin, pour faire l'extension des types, il vous suffit de mettre un code comme celui-ci:
declare module 'express' {
    export interface Request {
        property?: string;
    }
}
Hector Manuel
la source
-2

pourquoi avons-nous besoin de faire autant de tracas que dans les réponses acceptées ci-dessus, alors que nous pouvons nous en tirer juste cela

au lieu de joindre notre propriété à la demande , nous pouvons la joindre aux en- têtes de la demande

   req.headers[property] = "hello"
NxtCoder
la source