Estenda o objeto Express Request usando Typescript

127

Estou tentando adicionar uma propriedade para expressar o objeto de solicitação de um middleware usando o typescript. No entanto, não consigo descobrir como adicionar propriedades extras ao objeto. Eu prefiro não usar a notação de colchetes, se possível.

Estou procurando uma solução que me permita escrever algo semelhante a isto (se possível):

app.use((req, res, next) => {
    req.property = setProperty(); 
    next();
});
Isak Ågren
fonte
você deve ser capaz de estender a interface de solicitação que o arquivo express.d.ts fornece com os campos desejados.
toskv de

Respostas:

145

Você deseja criar uma definição personalizada e usar um recurso do texto digitalizado chamado Mesclagem de declarações . Isso é comumente usado, por exemplo, em method-override.

Criar um arquivo custom.d.tse certifique-se de incluí-lo em sua tsconfig.json's files-section se houver. O conteúdo pode ser o seguinte:

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

Isso permitirá que você, em qualquer ponto de seu código, use algo assim:

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
fonte
2
Acabei de fazer isso, mas fiz funcionar sem adicionar meu arquivo custom.d.ts à seção de arquivos em meu tsconfig.json, mas ainda funciona. Este é o comportamento esperado?
Chaim Friedman
1
@ChaimFriedman Sim. A filesseção restringe o conjunto de arquivos incluídos pelo TypeScript. Se você não especificar filesou include, todos *.d.tsserão incluídos por padrão, portanto, não há necessidade de adicionar suas tipificações personalizadas lá.
interphx
9
Não funciona para mim: Property 'tenantnão existe no tipo 'Solicitação' `Não faz diferença se eu incluí explicitamente tsconfig.jsonou não. ATUALIZAR Com declare globalcomo @basarat pointet fora em sua resposta funciona, mas eu tive que fazer import {Request} from 'express'primeiro.
Leão de
5
FWIW, esta resposta agora é obsoleta . A resposta de JCM é a maneira correta de aumentar o Requestobjeto em expresso (4.x pelo menos)
Eric Liprandi
3
Para pesquisas futuras - um bom exemplo, descobri que funcionou imediatamente
jd291
79

Conforme sugerido pelos comentários noindex.d.ts , você simplesmente declara ao Expressnamespace global quaisquer novos membros. Exemplo:

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

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

A extensão de namespaces globais é mais abordada em meu GitBook .

Basarat
fonte
Por que o global é necessário na declaração? O que acontece se não estiver lá?
Jason Kuhrt
Isso funciona com interfaces, mas no caso de alguém precisar mesclar tipos, observe que os tipos são "fechados" e não podem ser mesclados: github.com/Microsoft/TypeScript/issues/…
Peter W
Sr. @basarat, devo algumas cervejas a você.
marcellsimon
Também tive de adicionar ao meu tsconfig.json: {"compilerOptions": {"typeRoots": ["./src/typings/", "./node_modules/@types"]}, "files": ["./ src / typings / express / index.d.ts "]}
marcellsimon
Nenhuma das soluções acima funcionou .. mas esta funcionou na primeira execução .. muito obrigado .. !!
Ritesh
55

Para versões mais recentes do express, você precisa aumentar o express-serve-static-coremódulo.

Isso é necessário porque agora o objeto Express vem de lá: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/8fb0e959c2c7529b5fa4793a44b41b797ae671b9/types/express/index.d.ts#L19

Basicamente, use o seguinte:

declare module 'express-serve-static-core' {
  interface Request {
    myField?: string
  }
  interface Response {
    myField?: string
  }
}
JCM
fonte
1
Isso funcionou para mim, ao passo que estender o 'express'módulo antigo não funcionou. Obrigado!
Ben Kreeger
4
Estava lutando com isso, para fazer funcionar, tive que importar o módulo também:import {Express} from "express-serve-static-core";
andre_b
1
@andre_b Obrigado pela dica. Acho que a instrução import transforma o arquivo em um módulo, e essa é a parte necessária. Eu mudei para usar o export {}que também funciona.
Danyal Aytekin
2
Certifique-se de que o arquivo em que este código vai não seja chamado express.d.ts, caso contrário, o compilador tentará mesclá-lo com as digitações expressas, resultando em erros.
Tom Spencer
3
Certifique-se de que seus tipos devem ser os primeiros em typeRoots! types / express / index.d.ts and tsconfig => "typeRoots": ["./src/types", "./node_modules/@types"]
kaya
30

A resposta aceita (como as outras) não funciona para mim, mas

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

fez. Espero que ajude alguém.

max-lt
fonte
2
Método semelhante é descrito nos documentos ts em "Aumento do Módulo". É ótimo se você não quiser usar *.d.tsarquivos e apenas armazenar seus tipos em *.tsarquivos regulares .
im.pankratov
3
esta é a única coisa que funcionou para mim também, todas as outras respostas parecem precisar estar em arquivos .d.ts
parlamento
Isso também funciona para mim, desde que coloque meu custom-declarations.d.tsarquivo na raiz do projeto do TypeScript.
focorner
Eu estendi o tipo original para preservá-lo: import { Request as IRequest } from 'express/index';e interface Request extends IRequest. Também foi necessário adicionar o typeRoot
Ben Creasy
17

Depois de tentar 8 ou mais respostas e não ter sucesso. Eu finalmente consegui fazê-lo funcionar com jd291 comentário de apontamento para 3mards repo .

Crie um arquivo na base chamada types/express/index.d.ts. E nele escreva:

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

e incluí-lo em tsconfig.json:

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

Em seguida, yourPropertydeve estar acessível em todas as solicitações:

import express from 'express';

const app = express();

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

Nenhuma das soluções oferecidas funcionou para mim. Acabei simplesmente estendendo a interface do Request:

import {Request} from 'express';

export interface RequestCustom extends Request
{
    property: string;
}

Então, para usá-lo:

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

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

Edit : Versões recentes do TypeScript reclamam disso. Em vez disso, tive que fazer:

someMiddleware(expressRequest: Request, res: Response, next: NextFunction): void
{
    const req = expressRequest as RequestCustom;
    req.property = '';
}
Tom Mettam
fonte
1
isso funcionará, mas bastante detalhado se você tiver centenas de funções de middleware, amirite
Alexander Mills
1
@ user2473015 Sim, versões recentes do Typescript quebraram isso. Veja minha resposta atualizada.
Tom Mettam
8

No TypeScript, as interfaces são abertas. Isso significa que você pode adicionar propriedades a eles de qualquer lugar apenas redefinindo-os.

Considerando que você está usando este arquivo express.d.ts , você deve ser capaz de redefinir a interface de solicitação para adicionar o campo extra.

interface Request {
  property: string;
}

Então, em sua função de middleware, o parâmetro req também deve ter essa propriedade. Você deve ser capaz de usá-lo sem nenhuma alteração em seu código.

toskv
fonte
1
Como você "compartilha" essas informações em todo o código? Se eu definir uma propriedade em Request, dizer Request.user = {};em app.tscomo é que userController.tssabe sobre isso?
Nepoxx
2
@Nepoxx se você redefinir uma interface, o compilador irá mesclar as propriedades e torná-las visíveis em todos os lugares, é por isso. O ideal é que você faça a redefinição em um arquivo .d.ts. :)
toskv
Isso parece funcionar, no entanto, se eu usar o tipo express.Handler(em vez de especificar manualmente (req: express.Request, res: express.Response, next: express.NextFunction) => any)), ele não parece se referir ao mesmo Request, pois reclama que minha propriedade não existe.
Nepoxx
Eu não esperava que isso acontecesse, a menos que express.Handler estenda a interface Request. faz isso?
toskv
2
Posso fazer isso funcionar se usar, declare module "express"mas não se usar declare namespace Express. Eu prefiro usar a sintaxe de namespace, mas ela simplesmente não funciona para mim.
WillyC 01 de
5

Embora esta seja uma pergunta muito antiga, eu tropecei neste problema recentemente. A resposta aceita funciona bem, mas eu precisava adicionar uma interface personalizada Request- uma interface que eu estava usando em meu código e que não funcionava tão bem com o aceito responda. Logicamente, tentei isso:

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

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

Mas isso não funcionou porque o Typescript trata os .d.tsarquivos como importações globais e quando eles têm importações são tratados como módulos normais. É por isso que o código acima não funciona em uma configuração de texto digitado padrão.

Aqui está o que acabei fazendo

// typings/common.d.ts

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

export interface ITenant {
    ...
}
16kb
fonte
Isso funciona para meu arquivo principal, mas não em meus arquivos de roteamento ou controladores, não recebo nenhum linting, mas quando tento compilar diz "Propriedade 'usuário' não existe no tipo 'Solicitação'." (Estou usando usuário em vez de locatário), mas se eu adicionar // @ ts-ignore acima deles, então funciona (embora seja uma maneira boba de consertar, é claro. Você tem alguma ideia de por que pode não ser trabalhando para meus outros arquivos?
Logan,
Isso é uma coisa muito estranha @Logan. Você pode compartilhar sua .d.ts, tsconfig.jsonea instância uso? Além disso, qual versão do typescript você está usando, já que a importação em módulos globais só é compatível a partir do TS 2.9? Isso poderia ajudar melhor.
16kb
Eu carreguei os dados aqui, pastebin.com/0npmR1Zr Não tenho certeza por que o realce está todo bagunçado. Isto é do arquivo principal prnt.sc/n6xsyl Isto é de outro arquivo prnt.sc/n6xtp0 Claramente alguma parte de ele entende o que está acontecendo, mas o compilador não. Estou usando a versão 3.2.2 do texto datilografado
Logan
1
Surpreendentemente, ... "include": [ "src/**/*" ] ...funciona para mim, mas "include": ["./src/", "./src/Types/*.d.ts"],não funciona. Eu não tentei entender isso ainda
16kb
A interface de importação usando importações dinâmicas funciona para mim. Obrigado
Roman Mahotskyi
3

Talvez este problema tenha sido respondido, mas eu quero compartilhar um pouco, agora às vezes uma interface como outras respostas pode ser um pouco restritiva, mas podemos realmente manter as propriedades necessárias e, em seguida, adicionar quaisquer propriedades adicionais a serem adicionadas, criando um chave com um tipo de stringcom tipo de valor 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();
});

Portanto, agora também podemos adicionar qualquer propriedade adicional que quisermos a este objeto.

Eka putra
fonte
2

Se você está procurando uma solução que funcione com o express4, aqui está:

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

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

Chandara Chea
fonte
2

Todas essas respostas parecem estar erradas ou desatualizadas de uma forma ou de outra.

Isso funcionou para mim em maio de 2020:

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

import * as express from "express"

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

em tsconfig.json, adicione / mescle a propriedade de modo que:

"typeRoots": [ "@types" ]

Felicidades.

Will Brickner
fonte
Funciona com Webpack + Docker, import * pode ser substituído por export {};
Dooomel
1

Uma solução possível é usar "dupla fundição para qualquer"

1- definir uma interface com sua propriedade

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

2- elenco duplo

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

As vantagens da dupla fundição são:

  • a digitação está disponível
  • não polui as definições existentes, mas as estende, evitando confusão
  • uma vez que o elenco é explícito, ele compila as multas com a -noImplicitanybandeira

Como alternativa, existe a rota rápida (não digitada):

 req['myProperty'] = setProperty()

(não edite os arquivos de definição existentes com suas próprias propriedades - isso não pode ser mantido. Se as definições estiverem erradas, abra uma solicitação de pull)

EDITAR

Veja o comentário abaixo, fundição simples funciona neste caso req as MyRequest

Bruno Grieder
fonte
@akshay Neste caso, sim, porque MyRequestestende o http.IncomingMessage. Se não fosse o caso, a via dupla anyseria a única alternativa
Bruno Grieder
É recomendável que você lance para desconhecido em vez de qualquer.
dev
0

Esta resposta será benéfica para aqueles que confiam no pacote npm ts-node.

Eu também estava lutando com a mesma preocupação de estender o objeto de solicitação , segui muitas respostas no estouro de pilha e terminei seguindo a estratégia mencionada abaixo.

Declarei digitação estendida para expresso no seguinte diretório.${PROJECT_ROOT}/api/@types/express/index.d.ts

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

em seguida, atualizando meu tsconfig.jsonpara algo assim.

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

mesmo depois de fazer as etapas acima, o estúdio visual parou de reclamar, mas infelizmente, o ts-nodecompilador ainda costumava jogar.

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

Aparentemente, o ts-nodenão foi capaz de localizar as definições de tipo estendido para o objeto de solicitação .

Eventualmente, depois de passar horas, como eu sabia que o Código VS não estava reclamando e fui capaz de localizar as definições de digitação, sugerindo que algo está errado com o ts-nodecomplier.

Atualizar iniciar scriptem package.jsonconsertou para mim.

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

os --filesargumentos desempenham um papel fundamental aqui, determinando as definições de tipo personalizado.

Para obter mais informações, visite: https://github.com/TypeStrong/ts-node#help-my-types-are-missing

Divyanshu Rawat
fonte
0

Ajudar qualquer pessoa que esteja apenas procurando outra coisa para tentar aqui é o que funcionou para mim no final de maio de 2020, ao tentar estender a solicitação do ExpressJS. Tive que ter tentado mais de uma dúzia de coisas antes de fazer isso funcionar:

  • Inverta a ordem do que todos estão recomendando no "typeRoots" do seu tsconfig.json (e não se esqueça de descartar o caminho src se você tiver uma configuração rootDir no tsconfig, como "./src"). Exemplo:
"typeRoots": [
      "./node_modules/@types",
      "./your-custom-types-dir"
]
  • Exemplo de extensão personalizada ('./your-custom-types-dir/express/index.d.ts "). Tive que usar importação inline e exportações padrão para usar classes como um tipo em minha experiência para que isso também seja mostrado:
declare global {
  namespace Express {
    interface Request {
      customBasicProperty: string,
      customClassProperty: import("../path/to/CustomClass").default;
    }
  }
}
  • Atualize seu arquivo nodemon.json para adicionar o comando "--files" ao ts-node, exemplo:
{
  "restartable": "rs",
  "ignore": [".git", "node_modules/**/node_modules"],
  "verbose": true,
  "exec": "ts-node --files",
  "watch": ["src/"],
  "env": {
    "NODE_ENV": "development"
  },
  "ext": "js,json,ts"
}
Se for verdade
fonte
0

Pode ser que já seja tarde para essa resposta, mas de qualquer forma, aqui está como eu resolvi:

  1. Certifique-se de ter sua fonte de tipos incluída em seu tsconfig arquivo (pode ser um novo tópico)
  2. Dentro de seu diretório de tipos, adicione um novo diretório e nomeie-o como o pacote que deseja estender ou para o qual criar tipos. Neste caso específico, você criará um diretório com o nomeexpress
  3. Dentro do expressdiretório, crie um arquivo e nomeie-oindex.d.ts (DEVE SER EXATAMENTE ASSIM)
  4. Finalmente para fazer a extensão dos tipos você só precisa colocar um código como o seguinte:
declare module 'express' {
    export interface Request {
        property?: string;
    }
}
Hector Manuel
fonte
-1

Por que precisamos fazer tanto trabalho como nas respostas aceitas acima, quando podemos nos safar fazendo exatamente isso

em vez de anexar nossa propriedade à solicitação , podemos anexá-la aos cabeçalhos da solicitação

   req.headers[property] = "hello"
NxtCoder
fonte