Estendi l'oggetto Richiesta Express utilizzando Typescript


128

Sto cercando di aggiungere una proprietà per esprimere l'oggetto della richiesta da un middleware utilizzando il dattiloscritto. Tuttavia non riesco a capire come aggiungere proprietà extra all'oggetto. Preferirei non usare la notazione tra parentesi se possibile.

Sto cercando una soluzione che mi permetta di scrivere qualcosa di simile a questo (se possibile):

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

dovresti essere in grado di estendere l'interfaccia di richiesta fornita dal file express.d.ts con i campi desiderati.
toskv

Risposte:


147

Si desidera creare una definizione personalizzata e utilizzare una funzionalità in Typescript chiamata Dichiarazione di unione . Questo è comunemente usato, ad esempio in method-override.

Creare un file custom.d.tse fare in modo di includere nel vostro tsconfig.json's files-sezione se del caso. I contenuti possono apparire come segue:

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

Ciò ti consentirà, in qualsiasi punto del codice, di utilizzare qualcosa del genere:

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

2
L'ho appena fatto, ma l'ho fatto funzionare senza aggiungere il mio file custom.d.ts alla sezione dei file nel mio tsconfig.json, eppure funziona ancora. È questo il comportamento previsto?
Chaim Friedman

1
@ChaimFriedman Sì. La filessezione limita il set di file inclusi da TypeScript. Se non specifichi fileso include, *.d.tsvengono inclusi tutti per impostazione predefinita, quindi non è necessario aggiungere lì i tuoi caratteri personalizzati.
interphx il

9
Non funziona per me: ottengo Property 'tenantnon esiste sul tipo 'Richiesta' `` Non fa differenza se lo includo esplicitamente tsconfig.jsono meno. UPDATE Con declare globalcome @basarat Pointet fuori nelle sue opere answear, ma ho dovuto fare import {Request} from 'express'prima.
Lion

5
FWIW, questa risposta è ora obsoleta . La risposta di JCM è il modo corretto per aumentare ilRequest oggetto in expressjs (4.x almeno)
Eric Liprandi

3
Per ricerche future, un buon esempio che ho trovato che ha funzionato
immediatamente

79

Come suggerito dai commenti inindex.d.ts , devi semplicemente dichiarare allo Expressspazio dei nomi globale eventuali nuovi membri. Esempio:

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

Esempio completo:

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'estensione degli spazi dei nomi globali è trattata di più nel mio GitBook .


Perché è necessario globale nella dichiarazione? Cosa succede se non c'è?
Jason Kuhrt

Funziona con le interfacce, ma nel caso qualcuno abbia bisogno di unire i tipi, nota che i tipi sono "chiusi" e non possono essere uniti: github.com/Microsoft/TypeScript/issues/…
Peter W

Mr @basarat ti devo delle birre.
marcellsimon,

Ho anche dovuto aggiungere al mio tsconfig.json: {"compilerOptions": {"typeRoots": ["./src/typings/", "./node_modules/@types"]}, "files": ["./ src / typings / express / index.d.ts "]}
marcellsimon

Nessuna delle soluzioni precedenti ha funzionato .. ma questa ha funzionato in prima esecuzione .. grazie mille .. !!
Ritesh

55

Per le versioni più recenti di express, è necessario aumentare il express-serve-static-coremodulo.

Questo è necessario perché ora l'oggetto Express proviene da lì: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/8fb0e959c2c7529b5fa4793a44b41b797ae671b9/types/express/index.d.ts#L19

Fondamentalmente, usa quanto segue:

declare module 'express-serve-static-core' {
  interface Request {
    myField?: string
  }
  interface Response {
    myField?: string
  }
}

1
Questo ha funzionato per me, mentre l'estensione del vecchio 'express'modulo non funzionava. Grazie!
Ben Kreeger

4
Stavo lottando con questo, per farlo funzionare, ho dovuto importare anche il modulo:import {Express} from "express-serve-static-core";
andre_b

1
@andre_b Grazie per il suggerimento. Penso che l'istruzione import trasformi il file in un modulo, e questa è la parte necessaria. Sono passato all'utilizzo export {}che funziona anche.
Danyal Aytekin

2
Assicurati che il file in cui questo codice va inserito non sia chiamato express.d.ts, altrimenti il ​​compilatore proverà a unirlo alle digitazioni rapide, causando errori.
Tom Spencer

3
Assicurati che i tuoi tipi debbano essere i primi in typeRoots! types / express / index.d.ts e tsconfig => "typeRoots": ["./src/types", "./node_modules/@types"]
kaya

31

La risposta accettata (come le altre) non funziona per me ma

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

fatto. Spero che possa aiutare qualcuno.


2
Un metodo simile è descritto nella documentazione di ts sotto "Modulo di aumento". È fantastico se non vuoi usare i *.d.tsfile e memorizzare i tuoi tipi all'interno di *.tsfile normali .
im.pankratov

3
questa è l'unica cosa che ha funzionato anche per me, tutte le altre risposte sembrano dover essere nei file .d.ts
parlamento

Questo funziona anche per me, a condizione che metta il mio custom-declarations.d.tsfile nella radice del progetto TypeScript.
focorner

Ho esteso il tipo originale per conservarlo: import { Request as IRequest } from 'express/index';e interface Request extends IRequest. Inoltre ho dovuto aggiungere il tipoRoot
Ben Creasy

17

Dopo aver provato circa 8 risposte e non aver avuto successo. Alla fine sono riuscito a farlo funzionare con il commento di jd291 che punta al repository 3mards .

Crea un file nella base chiamato types/express/index.d.ts. E in esso scrivi:

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

e includilo tsconfig.jsoncon:

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

Quindi yourPropertydovrebbe essere accessibile ad ogni richiesta:

import express from 'express';

const app = express();

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

14

Nessuna delle soluzioni offerte ha funzionato per me. Ho finito per estendere semplicemente l'interfaccia della richiesta:

import {Request} from 'express';

export interface RequestCustom extends Request
{
    property: string;
}

Quindi per usarlo:

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

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

Modifica : le versioni recenti di TypeScript si lamentano di questo. Invece ho dovuto fare:

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

1
funzionerà, ma abbastanza dettagliato se hai centinaia di funzioni middleware, amirite
Alexander Mills

1
@ user2473015 Sì, le versioni recenti di Typescript hanno rotto questo. Vedi la mia risposta aggiornata.
Tom Mettam

8

In TypeScript, le interfacce sono aperte. Ciò significa che puoi aggiungervi proprietà da qualsiasi luogo semplicemente ridefinendole.

Considerando che stai utilizzando questo file express.d.ts , dovresti essere in grado di ridefinire l' interfaccia della richiesta per aggiungere il campo extra.

interface Request {
  property: string;
}

Quindi nella funzione middleware, anche il parametro req dovrebbe avere questa proprietà. Dovresti essere in grado di usarlo senza modifiche al tuo codice.


1
Come "condividi" queste informazioni in tutto il codice? Se definisco una proprietà in Request, diciamo Request.user = {};in app.tscome la userController.tsconosco?
Nepoxx

2
@Nepoxx se ridefinisci un'interfaccia il compilatore unirà le proprietà e le renderà visibili ovunque, ecco perché. Idealmente faresti la ridefinizione in un file .d.ts. :)
toskv

Sembra funzionare, tuttavia se uso il tipo express.Handler(invece di specificare manualmente (req: express.Request, res: express.Response, next: express.NextFunction) => any)), non sembra riferirsi allo stesso in Requestquanto si lamenta che la mia proprietà non esiste.
Nepoxx

Non me lo aspetterei, a meno che express.Handler estende l'interfaccia di richiesta. lo fa?
toskv

2
Posso farlo funzionare se lo uso, declare module "express"ma non se lo uso declare namespace Express. Preferisco usare la sintassi dello spazio dei nomi ma non funziona per me.
WillyC

5

Sebbene questa sia una domanda molto vecchia, ultimamente mi sono imbattuto in questo problema.La risposta accettata funziona bene ma avevo bisogno di aggiungere un'interfaccia personalizzata a Request- un'interfaccia che avevo utilizzato nel mio codice e che non funzionava così bene con risposta. Logicamente, ho provato questo:

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

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

Ma ciò non ha funzionato perché Typescript tratta i .d.tsfile come importazioni globali e quando contengono importazioni vengono trattati come normali moduli. Ecco perché il codice sopra non funziona su un'impostazione di dattiloscritto standard.

Ecco cosa ho finito per fare

// typings/common.d.ts

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

export interface ITenant {
    ...
}

Funziona per il mio file principale, ma non nei miei file di routing o controller, non ricevo lint, ma quando provo a compilare dice "La proprietà 'utente' non esiste nel tipo 'Richiesta'." (Sto usando user invece di tenant), ma se aggiungo // @ ts-ignore sopra di loro, allora funziona (anche se è un modo stupido per risolverlo, ovviamente. Hai qualche idea sul perché potrebbe non essere lavora per gli altri miei file?
Logan

Questa è una cosa molto strana @Logan. Puoi condividere il tuo .d.ts, tsconfig.jsone l'istanza uso? Inoltre, quale versione di dattiloscritto stai utilizzando poiché questa importazione nei moduli globali è supportata solo a partire da TS 2.9? Questo potrebbe aiutare meglio.
16 kb

Ho caricato i dati qui, pastebin.com/0npmR1Zr Non sono sicuro del motivo per cui l'evidenziazione è tutta incasinata sebbene Questo provenga dal file principale prnt.sc/n6xsyl Questo proviene da un altro file prnt.sc/n6xtp0 Chiaramente una parte di capisce cosa sta succedendo, ma il compilatore no. Sto usando la versione 3.2.2 del dattiloscritto
Logan

1
Sorprendentemente, ... "include": [ "src/**/*" ] ...funziona per me ma "include": ["./src/", "./src/Types/*.d.ts"],non lo fa. Non sono ancora andato in profondità nel cercare di capirlo
16kb

L'importazione dell'interfaccia utilizzando le importazioni dinamiche funziona per me. Grazie
Roman Mahotskyi

3

Forse questo problema ha avuto una risposta, ma voglio condividere solo un po ', ora a volte un'interfaccia come le altre risposte può essere un po' troppo restrittiva, ma possiamo effettivamente mantenere le proprietà richieste e quindi aggiungere eventuali proprietà aggiuntive da aggiungere creando un chiave con un tipo di stringcon tipo di valore diany

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

Quindi ora, possiamo anche aggiungere qualsiasi proprietà aggiuntiva che vogliamo a questo oggetto.


3

Tutte queste risposte sembrano essere sbagliate o obsolete in un modo o nell'altro.

Questo ha funzionato per me a maggio 2020:

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

import * as express from "express"

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

in tsconfig.json, aggiungi / unisci la proprietà in modo che:

"typeRoots": [ "@types" ]

Saluti.


Funziona con Webpack + Docker, import * può essere sostituito con export {};
Dooomel

2

Se stai cercando una soluzione che funzioni con express4, eccola:

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

Rif da https://github.com/TypeStrong/ts-node/issues/715#issuecomment-526757308


1

Una possibile soluzione è utilizzare "double casting to any"

1- definisci un'interfaccia con la tua proprietà

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

2- doppio cast

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

I vantaggi della doppia fusione sono che:

  • è disponibile la digitazione
  • non inquina le definizioni esistenti ma le amplia, evitando confusione
  • poiché il casting è esplicito, compila le multe con il -noImplicitanyflag

In alternativa, c'è il percorso veloce (non tipizzato):

 req['myProperty'] = setProperty()

(non modificare i file di definizione esistenti con le tue proprietà: non è possibile mantenerli. Se le definizioni sono sbagliate, apri una richiesta pull)

MODIFICARE

Vedi il commento qui sotto, il casting semplice funziona in questo caso req as MyRequest


@akshay In questo caso sì, perché MyRequestestende il file http.IncomingMessage. Se non fosse così, il doppio casting via anysarebbe l'unica alternativa
Bruno Grieder

Si consiglia di eseguire il cast su unknown invece che su any.
dev

0

Questa risposta sarà vantaggiosa per coloro che si affidano al pacchetto npm ts-node .

Stavo anche lottando con la stessa preoccupazione di estendere l' oggetto richiesta , ho seguito molte risposte in stack-overflow e ho finito con la strategia sotto menzionata.

Ho dichiarato la digitazione estesa per express nella seguente directory.${PROJECT_ROOT}/api/@types/express/index.d.ts

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

quindi aggiornare il mio tsconfig.jsona qualcosa di simile.

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

anche dopo aver eseguito i passaggi precedenti, lo studio visivo ha smesso di lamentarsi, ma sfortunatamente il ts-nodecompilatore continuava a lanciare.

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

Apparentemente, ts-nodenon è stato possibile individuare le definizioni di tipo estese per la richiesta oggetto .

Alla fine, dopo aver trascorso ore, poiché sapevo che il codice VS non si lamentava ed era in grado di individuare le definizioni di battitura, il che implica che qualcosa non va ts-node compilatore.

Aggiornamento inizio scriptin package.jsonfissa per me.

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

gli --filesargomenti giocano un ruolo chiave qui trovano la determinazione delle definizioni di tipo personalizzato.

Per ulteriori informazioni, visitare: https://github.com/TypeStrong/ts-node#help-my-types-are-missing


0

Aiutare chiunque stia solo cercando qualcos'altro da provare qui è ciò che ha funzionato per me alla fine di maggio del 2020 quando ho cercato di estendere la richiesta di ExpressJS. Ho dovuto provare più di una dozzina di cose prima di farlo funzionare:

  • Cambia l'ordine di ciò che tutti consigliano in "typeRoots" del tuo tsconfig.json (e non dimenticare di eliminare il percorso src se hai un'impostazione rootDir in tsconfig come "./src"). Esempio:
"typeRoots": [
      "./node_modules/@types",
      "./your-custom-types-dir"
]
  • Esempio di estensione personalizzata ("./your-custom-types-dir/express/index.d.ts"). Ho dovuto utilizzare l'importazione in linea e le esportazioni predefinite per utilizzare le classi come tipo nella mia esperienza, quindi anche questo viene mostrato:
declare global {
  namespace Express {
    interface Request {
      customBasicProperty: string,
      customClassProperty: import("../path/to/CustomClass").default;
    }
  }
}
  • Aggiorna il tuo file nodemon.json per aggiungere il comando "--files" a ts-node, esempio:
{
  "restartable": "rs",
  "ignore": [".git", "node_modules/**/node_modules"],
  "verbose": true,
  "exec": "ts-node --files",
  "watch": ["src/"],
  "env": {
    "NODE_ENV": "development"
  },
  "ext": "js,json,ts"
}

0

Potrebbe essere già abbastanza tardi per questa risposta, ma comunque ecco come ho risolto:

  1. Assicurati di avere la tua fonte di tipi inclusa nel tuo tsconfigfile (questo potrebbe essere un thread completamente nuovo)
  2. All'interno della directory dei tipi, aggiungere una nuova directory e denominarla come il pacchetto per il quale si desidera estendere o creare tipi. In questo caso specifico, creerai una directory con il nomeexpress
  3. All'interno della expressdirectory creare un file e nominarlo index.d.ts(DEVE ESSERE ESATTAMENTE COME QUELLO)
  4. Infine per realizzare l'estensione dei tipi basta inserire un codice come il seguente:
declare module 'express' {
    export interface Request {
        property?: string;
    }
}

-2

perché abbiamo bisogno di fare così tante seccature come nelle risposte accettate sopra, quando possiamo farla franca facendo proprio questo

invece di allegare la nostra proprietà alla richiesta , possiamo allegarla alle intestazioni della richiesta

   req.headers[property] = "hello"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.