Rozszerz obiekt Express Request przy użyciu Typescript

128

Próbuję dodać właściwość, aby wyrazić obiekt żądania z oprogramowania pośredniego przy użyciu skryptu. Jednak nie mogę dowiedzieć się, jak dodać dodatkowe właściwości do obiektu. Wolałbym nie używać notacji w nawiasach, jeśli to możliwe.

Szukam rozwiązania, które pozwoliłoby mi napisać coś podobnego do tego (jeśli to możliwe):

app.use((req, res, next) => {
    req.property = setProperty(); 
    next();
});
Isak Ågren
źródło
powinieneś być w stanie rozszerzyć interfejs żądania, który zapewnia plik express.d.ts, o żądane pola.
toskv

Odpowiedzi:

147

Chcesz utworzyć niestandardową definicję i użyć funkcji w Typescript o nazwie Deklaracja scalania . Jest to powszechnie stosowane, np method-override. W.

Utwórz plik custom.d.tsi upewnij się, aby umieścić go w tsconfig.json„s files-section jeśli występują. Zawartość może wyglądać następująco:

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

Pozwoli ci to w dowolnym miejscu kodu użyć czegoś takiego:

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
źródło
2
Właśnie to zrobiłem, ale udało mi się to bez dodawania mojego pliku custom.d.ts do sekcji plików w moim tsconfig.json, ale nadal działa. Czy to oczekiwane zachowanie?
Chaim Friedman
1
@ChaimFriedman Yes. filesSekcja ogranicza zestaw plików zawartych przez maszynopis. Jeśli nie określisz fileslub include, wszystkie *.d.tszostaną uwzględnione domyślnie, więc nie ma potrzeby dodawania tam niestandardowych typów.
interphx
9
Nie działa dla mnie: otrzymuję Property 'tenantnie istnieje w typie `` Żądanie ''. `` Nie ma znaczenia, czy wyraźnie to uwzględnię, tsconfig.jsonczy nie. AKTUALIZACJA Z declare globalas @basarat pointet w swojej pracy Answear, ale import {Request} from 'express'najpierw musiałem to zrobić .
Lion,
5
FWIW, ta odpowiedź jest już nieaktualna . Odpowiedź JCM to właściwy sposób na powiększenie Requestobiektu w expressjs (przynajmniej 4.x)
Eric Liprandi
3
Do przyszłych wyszukiwań - dobry przykład, który znalazłem po wyjęciu z pudełka: github.com/3mard/ts-node-example
jd291
79

Jak sugerują komentarze windex.d.ts , po prostu deklarujesz w globalnej Expressprzestrzeni nazw wszystkich nowych członków. Przykład:

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

Pełny przykład:

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

Rozszerzanie globalnych przestrzeni nazw jest szerzej omówione w moim GitBooku .

basarat
źródło
Dlaczego w deklaracji potrzebne jest globalne? Co się stanie, jeśli go tam nie ma?
Jason Kuhrt
Działa to z interfejsami, ale jeśli ktoś potrzebuje scalić typy, pamiętaj, że typy są „zamknięte” i nie mogą być łączone: github.com/Microsoft/TypeScript/issues/ ...
Peter W
Panie @basarat, jestem panu winien kilka piw.
marcellsimon
Musiałem również dodać do mojego tsconfig.json: {"compilerOptions": {"typeRoots": ["./src/typings/", "./node_modules/@types"]}, "files": ["./ src / typings / express / index.d.ts "]}
marcellsimon
Żadne z powyższych rozwiązań nie zadziałało .. ale to zadziałało za pierwszym razem .. wielkie dzięki .. !!
Ritesh
55

W przypadku nowszych wersji ekspresu musisz rozszerzyć express-serve-static-coremoduł.

Jest to potrzebne, ponieważ teraz obiekt Express pochodzi stamtąd: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/8fb0e959c2c7529b5fa4793a44b41b797ae671b9/types/express/index.d.ts#L19

Zasadniczo użyj następujących:

declare module 'express-serve-static-core' {
  interface Request {
    myField?: string
  }
  interface Response {
    myField?: string
  }
}
JCM
źródło
1
To zadziałało dla mnie, podczas gdy rozszerzenie zwykłego starego 'express'modułu nie. Dziękuję Ci!
Ben Kreeger
4
Walczyłem z tym, aby to działało, musiałem również zaimportować moduł:import {Express} from "express-serve-static-core";
andre_b
1
@andre_b Dzięki za podpowiedź. Myślę, że instrukcja import zamienia plik w moduł, a to jest część, która jest konieczna. Przełączyłem się na używanie, export {}które również działa.
Danyal Aytekin
2
Upewnij się, że plik, do którego trafia ten kod, nie jest wywoływany express.d.ts, w przeciwnym razie kompilator spróbuje scalić go z typami ekspresowymi, co spowoduje błędy.
Tom Spencer
3
Upewnij się, że twoje typy muszą być pierwsze w typeRoots! types / express / index.d.ts and tsconfig => "typeRoots": ["./src/types", "./node_modules/@types"]
kaya
31

Zaakceptowana odpowiedź (podobnie jak inne) nie działa dla mnie, ale

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

zrobił. Mam nadzieję, że to komuś pomoże.

max-lt
źródło
2
Podobna metoda jest opisana w dokumentacji ts w sekcji „Rozszerzenie modułu”. To świetnie, jeśli nie chcesz używać *.d.tsplików i po prostu przechowujesz swoje typy w zwykłych *.tsplikach.
im.pankratov
3
to jest jedyna rzecz, która też działała dla mnie, wszystkie inne odpowiedzi wydają się znajdować w plikach .d.ts
parlament
Działa to również dla mnie, pod warunkiem, że umieszczę mój custom-declarations.d.tsplik w katalogu głównym projektu TypeScript.
focorner
Rozszerzyłem oryginalny typ, aby go zachować: import { Request as IRequest } from 'express/index';i interface Request extends IRequest. Musiałem także dodać typeRoot
Ben Creasy
17

Po wypróbowaniu 8 lub więcej odpowiedzi bez powodzenia. W końcu udało mi się to uruchomić z komentarzem jd291 wskazującym na repozytorium 3mards .

Utwórz plik w bazie o nazwie types/express/index.d.ts. A w nim napisz:

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

i dołącz go tsconfig.jsondo:

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

Następnie yourPropertypowinien być dostępny pod każdym żądaniem:

import express from 'express';

const app = express();

app.get('*', (req, res) => {
    req.yourProperty = 
});
kaleidawave
źródło
14

Żadne z oferowanych rozwiązań nie zadziałało. Skończyło się na rozszerzeniu interfejsu żądania:

import {Request} from 'express';

export interface RequestCustom extends Request
{
    property: string;
}

Następnie, aby go użyć:

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

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

Edycja : najnowsze wersje języka TypeScript narzekają na to. Zamiast tego musiałem zrobić:

someMiddleware(expressRequest: Request, res: Response, next: NextFunction): void
{
    const req = expressRequest as RequestCustom;
    req.property = '';
}
Tom Mettam
źródło
1
to zadziała, ale dość rozwlekłe, jeśli masz setki funkcji oprogramowania pośredniego, amirite
Alexander Mills,
1
@ user2473015 Tak, ostatnie wersje Typescript to zepsuły. Zobacz moją zaktualizowaną odpowiedź.
Tom Mettam,
8

W języku TypeScript interfejsy są otwarte. Oznacza to, że możesz dodawać do nich właściwości z dowolnego miejsca, po prostu zmieniając ich definicję.

Biorąc pod uwagę, że używasz tego pliku express.d.ts , powinieneś być w stanie przedefiniować interfejs żądania , aby dodać dodatkowe pole.

interface Request {
  property: string;
}

Następnie w funkcji oprogramowania pośredniego parametr req również powinien mieć tę właściwość. Powinieneś móc go używać bez żadnych zmian w kodzie.

toskv
źródło
1
Jak „udostępniasz” te informacje w całym kodzie? Gdybym zdefiniowanie obiektu Request, powiedzieć, Request.user = {};w app.tsjaki sposób userController.tso tym wiedzieć?
Nepoxx
2
@Nepoxx, jeśli przedefiniujesz interfejs, kompilator połączy właściwości i sprawi, że będą widoczne wszędzie, dlatego. Idealnie byłoby gdybyś dokonał redefinicji w pliku .d.ts. :)
toskv
Wydaje się, że to działa, jednak jeśli używam typu express.Handler(zamiast ręcznego określania (req: express.Request, res: express.Response, next: express.NextFunction) => any)), wydaje się, że nie odnosi się do tego samego, Requestponieważ skarży się, że moja właściwość nie istnieje.
Nepoxx
Nie spodziewałbym się tego, chyba że express.Handler rozszerzy interfejs Request. czy to?
toskv
2
Mogę to zrobić, jeśli używam, declare module "express"ale nie, jeśli używam declare namespace Express. Wolałbym raczej używać składni przestrzeni nazw, ale to po prostu nie działa dla mnie.
WillyC
5

Chociaż jest to bardzo stare pytanie, ostatnio natknąłem się na ten problem. Zaakceptowana odpowiedź działa dobrze, ale musiałem dodać niestandardowy interfejs do Request- interfejsu, którego używałem w swoim kodzie i który nie działał tak dobrze z zaakceptowanym odpowiedź. Logicznie rzecz biorąc, spróbowałem tego:

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

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

Ale to nie zadziałało, ponieważ Typescript traktuje .d.tspliki jako import globalny, a kiedy mają w sobie import, są traktowane jak zwykłe moduły. Dlatego powyższy kod nie działa na standardowym ustawieniu maszynopisu.

Oto, co ostatecznie zrobiłem

// typings/common.d.ts

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

export interface ITenant {
    ...
}
16kb
źródło
Działa to w przypadku mojego głównego pliku, ale nie w moich plikach routingu ani kontrolerach, nie ma lintingu, ale kiedy próbuję skompilować, pojawia się komunikat „Właściwość 'użytkownik' nie istnieje dla typu 'Żądanie'.” (Używam użytkownika zamiast dzierżawcy), ale jeśli dodam nad nimi // @ ts-ignore, to działa (chociaż jest to oczywiście głupi sposób, aby to naprawić. Czy masz jakieś przemyślenia, dlaczego tak nie jest pracuje dla moich innych plików?
Logan
To bardzo dziwna rzecz @Logan. Można udostępniać .d.ts, tsconfig.jsona wystąpienie użyciu? Ponadto, jakiej wersji maszynopisu używasz, ponieważ importowanie w modułach globalnych jest obsługiwane tylko od wersji TS 2.9? To mogłoby pomóc lepiej.
16kb
Przesłałem dane tutaj, pastebin.com/0npmR1Zr Nie jestem pewien, dlaczego podświetlenie jest pomieszane To pochodzi z głównego pliku prnt.sc/n6xsyl To jest z innego pliku prnt.sc/n6xtp0 Najwyraźniej jakaś część rozumie, co się dzieje, ale kompilator tego nie robi. Używam wersji 3.2.2 maszynopisu
Logan
1
O dziwo, ... "include": [ "src/**/*" ] ...działa dla mnie, ale "include": ["./src/", "./src/Types/*.d.ts"],nie. Nie wdałem się jeszcze w próbę zrozumienia tego
16kb
Interfejs importu za pomocą importu dynamicznego działa dla mnie. Dzięki
Roman Mahotskyi
3

Być może ten problem został rozwiązany, ale chcę się trochę podzielić, teraz czasami interfejs taki jak inne odpowiedzi może być trochę zbyt restrykcyjny, ale możemy faktycznie zachować wymagane właściwości, a następnie dodać dodatkowe właściwości, które mają zostać dodane, tworząc klucz z typem stringz typem wartościany

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

Więc teraz możemy również dodać dowolną dodatkową właściwość, którą chcemy, do tego obiektu.

Eka putra
źródło
3

Wszystkie te odpowiedzi wydają się być błędne lub w jakiś sposób nieaktualne.

To zadziałało dla mnie w maju 2020 roku:

w ${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, dodaj / połącz właściwość w taki sposób, że:

"typeRoots": [ "@types" ]

Twoje zdrowie.

Will Brickner
źródło
Działa z Webpack + Docker, import * można zastąpić eksportem {};
Dooomel
2

Jeśli szukasz rozwiązania współpracującego z express4, oto jest:

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

Odniesienie z https://github.com/TypeStrong/ts-node/issues/715#issuecomment-526757308

Chandara Chea
źródło
1

Jednym z możliwych rozwiązań jest użycie „podwójnego odlewania do dowolnego”

1- Zdefiniuj interfejs ze swoją nieruchomością

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

2- podwójny odlew

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

Zalety podwójnego odlewania to:

  • typy są dostępne
  • nie zanieczyszcza istniejących definicji, ale je rozszerza, unikając nieporozumień
  • ponieważ rzutowanie jest jawne, kompiluje grzywny z -noImplicitanyflagą

Alternatywnie istnieje trasa szybka (bez typu):

 req['myProperty'] = setProperty()

(nie edytuj istniejących plików definicji z własnymi właściwościami - jest to niemożliwe do utrzymania. Jeśli definicje są nieprawidłowe, otwórz żądanie ściągnięcia)

EDYTOWAĆ

Zobacz komentarz poniżej, proste odlewanie działa w tym przypadku req as MyRequest

Bruno Grieder
źródło
@akshay W tym przypadku tak, ponieważ MyRequestrozszerza rozszerzenie http.IncomingMessage. anyGdyby tak nie było, jedyną alternatywą byłby podwójny casting przez
Bruno Grieder
Zaleca się, aby zamiast dowolnego przesyłać na nieznane.
dev
0

Ta odpowiedź będzie korzystna dla tych, którzy polegają na pakiecie npm ts-node.

Zmagałem się również z tym samym problemem rozszerzania obiektu żądania , śledziłem wiele odpowiedzi w przepełnieniu stosu i zakończyłem zgodnie z poniższą strategią.

W następującym katalogu zadeklarowałem rozszerzone typowanie dla express .${PROJECT_ROOT}/api/@types/express/index.d.ts

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

następnie zaktualizuj mój tsconfig.jsondo czegoś takiego.

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

nawet po wykonaniu powyższych czynności studio wizualne przestało narzekać, ale niestety ts-nodekompilator wciąż wrzucał.

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

Najwyraźniej ts-nodenie był w stanie zlokalizować rozszerzonych definicji typu dla obiektu żądania .

W końcu po spędzeniu wielu godzin, ponieważ wiedziałem, że VS Code nie narzeka i był w stanie zlokalizować definicje pisania, sugerując, że coś jest nie tak z ts-nodeComplier.

Aktualizacja Start scriptw package.jsonnaprawiła to za mnie.

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

te --filesargumenty odgrywają kluczową rolę tu znaleźć określenia definicji niestandardowego typu.

Więcej informacji można znaleźć na stronie : https://github.com/TypeStrong/ts-node#help-my-types-are-missing

Divyanshu Rawat
źródło
0

Pomaganie każdemu, kto szuka czegoś innego do wypróbowania, jest tym, co zadziałało dla mnie pod koniec maja 2020 r., Kiedy próbowałem przedłużyć żądanie ExpressJS. Musiałem wypróbować więcej niż tuzin rzeczy, zanim to zadziałało:

  • Odwróć kolejność tego, co wszyscy zalecają w "typeRoots" twojego tsconfig.json (i nie zapomnij porzucić ścieżki src, jeśli masz ustawienie rootDir w tsconfig, takie jak "./src"). Przykład:
"typeRoots": [
      "./node_modules/@types",
      "./your-custom-types-dir"
]
  • Przykład rozszerzenia niestandardowego („./your-custom-types-dir/express/index.d.ts”). Musiałem użyć funkcji importu wbudowanego i domyślnego eksportu, aby używać klas jako typu z mojego doświadczenia, więc to również zostało pokazane:
declare global {
  namespace Express {
    interface Request {
      customBasicProperty: string,
      customClassProperty: import("../path/to/CustomClass").default;
    }
  }
}
  • Zaktualizuj plik nodemon.json, aby dodać polecenie „--files” do węzła ts, na przykład:
{
  "restartable": "rs",
  "ignore": [".git", "node_modules/**/node_modules"],
  "verbose": true,
  "exec": "ts-node --files",
  "watch": ["src/"],
  "env": {
    "NODE_ENV": "development"
  },
  "ext": "js,json,ts"
}
Jeśli prawda
źródło
0

Na tę odpowiedź może być już dość późno, ale tak czy inaczej, oto jak rozwiązałem:

  1. Upewnij się, że masz w tsconfigpliku źródło typów (może to być zupełnie nowy wątek)
  2. W katalogu typów dodaj nowy katalog i nazwij go jako pakiet, dla którego chcesz rozszerzyć lub utworzyć typy. W tym konkretnym przypadku utworzysz katalog o nazwieexpress
  3. W expresskatalogu utwórz plik i nazwij go index.d.ts(MUSI BYĆ DOKŁADNIE TAK JAK)
  4. Na koniec, aby rozszerzyć typy, wystarczy umieścić kod podobny do następującego:
declare module 'express' {
    export interface Request {
        property?: string;
    }
}
Hector Manuel
źródło
-2

dlaczego musimy robić tyle kłopotów, jak w powyższych zaakceptowanych odpowiedziach, skoro możemy to zrobić na sucho

zamiast dołączać naszą własność do żądania , możemy dołączyć ją do nagłówków żądania

   req.headers[property] = "hello"
NxtCoder
źródło