Extender el objeto de solicitud rápida usando TypeScript

128

Estoy tratando de agregar una propiedad para expresar el objeto de solicitud desde un middleware usando mecanografiado. Sin embargo, no puedo averiguar cómo agregar propiedades adicionales al objeto. Preferiría no usar la notación de corchetes si es posible.

Estoy buscando una solución que me permita escribir algo similar a esto (si es posible):

app.use((req, res, next) => {
    req.property = setProperty(); 
    next();
});
Isak Ågren
fuente
debería poder ampliar la interfaz de solicitud que proporciona el archivo express.d.ts con los campos que desee.
toskv

Respuestas:

146

Desea crear una definición personalizada y utilizar una función en TypeScript llamada Fusión de declaraciones . Esto se usa comúnmente, por ejemplo, enmethod-override .

Cree un archivo custom.d.tsy asegúrese de incluirlo en su sección tsconfig.json's, filessi la hubiera. El contenido puede tener el siguiente aspecto:

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

Esto le permitirá, en cualquier punto de su código, usar algo como esto:

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
fuente
2
Acabo de hacer esto, pero lo hice funcionar sin agregar mi archivo custom.d.ts a la sección de archivos en mi tsconfig.json, pero todavía funciona. ¿Es este el comportamiento esperado?
Chaim Friedman
1
@ChaimFriedman Sí. La filessección restringe el conjunto de archivos incluidos por TypeScript. Si no especifica fileso include, entonces todos *.d.tsse incluyen de forma predeterminada, por lo que no es necesario agregar sus mecanografiados personalizados allí.
interphx
9
No funciona para mí: obtengo Property 'tenantno existe en el tipo 'Solicitud'. No importa si lo incluyo explícitamente tsconfig.jsono no. ACTUALIZAR Con declare global@basarat pointet en sus trabajos de answear, pero tenía que hacerlo import {Request} from 'express'primero.
León
5
FWIW, esta respuesta ahora es obsoleta . La respuesta de JCM es la forma correcta de aumentar el Requestobjeto en expressjs (4.x al menos)
Eric Liprandi
3
Para búsquedas futuras, encontré un buen ejemplo que funcionó de inmediato
jd291
79

Como lo sugieren los comentarios en elindex.d.ts , simplemente declare en el Expressespacio de nombres global cualquier miembro nuevo. Ejemplo:

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

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

Se cubre la extensión de los espacios de nombres globales más en mi GitBook .

basarat
fuente
¿Por qué se necesita global en la declaración? ¿Qué pasa si no está ahí?
Jason Kuhrt
Esto funciona con interfaces, pero en caso de que alguien necesite combinar tipos, tenga en cuenta que los tipos están "cerrados" y no se pueden combinar: github.com/Microsoft/TypeScript/issues/…
Peter W
Señor @basarat, le debo unas cervezas.
marcellsimon
También tuve que agregar a mi tsconfig.json: {"compilerOptions": {"typeRoots": ["./src/typings/", "./node_modules/@types"]}, "files": ["./ src / typings / express / index.d.ts "]}
marcellsimon
Ninguna de las soluciones anteriores funcionó .. pero esta hizo el trabajo en la primera ejecución .. muchas gracias .. !!
Ritesh
55

Para las versiones más recientes de express, necesita aumentar el express-serve-static-coremódulo.

Esto es necesario porque ahora el objeto Express proviene de allí: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/8fb0e959c2c7529b5fa4793a44b41b797ae671b9/types/express/index.d.ts#L19

Básicamente, usa lo siguiente:

declare module 'express-serve-static-core' {
  interface Request {
    myField?: string
  }
  interface Response {
    myField?: string
  }
}
JCM
fuente
1
Esto funcionó para mí, mientras que extender el 'express'módulo simple no funcionó. ¡Gracias!
Ben Kreeger
4
Estaba luchando con esto, para que esto funcionara, también tuve que importar el módulo:import {Express} from "express-serve-static-core";
andre_b
1
@andre_b Gracias por la pista. Creo que la declaración de importación convierte el archivo en un módulo, y esta es la parte necesaria. Cambié a usar export {}que también funciona.
Danyal Aytekin
2
Asegúrese de que no se llame al archivo en el que va este código express.d.ts; de lo contrario, el compilador intentará fusionarlo con los tipos expresos, lo que generará errores.
Tom Spencer
3
¡Asegúrese de que sus tipos deben ser los primeros en typeRoots! types / express / index.d.ts y tsconfig => "typeRoots": ["./src/types", "./node_modules/@types"]
kaya
30

La respuesta aceptada (como las demás) no me funciona, pero

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

hizo. Espero que ayude a alguien.

max-lt
fuente
2
Un método similar se describe en los documentos ts en "Aumento de módulos". Es genial si no desea utilizar *.d.tsarchivos y simplemente almacenar sus tipos dentro de *.tsarchivos normales .
im.pankratov
3
esto es lo único que funcionó para mí también, todas las demás respuestas parecen necesitar estar en archivos .d.ts
parlamento
Esto también funciona para mí, siempre que coloque mi custom-declarations.d.tsarchivo en la raíz del proyecto de TypeScript.
focorner
Extendí el tipo original para conservarlo: import { Request as IRequest } from 'express/index';y interface Request extends IRequest. También tuve que agregar el typeRoot
Ben Creasy
17

Después de intentar 8 o más respuestas y no tener éxito. Finalmente logré conseguir que funcione con jd291 comentario que señala 's a 3mards Repo .

Cree un archivo en la base llamado types/express/index.d.ts. Y en él escribe:

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

e incluirlo tsconfig.jsoncon:

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

Entonces yourPropertydebería ser accesible en cada solicitud:

import express from 'express';

const app = express();

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

Ninguna de las soluciones ofrecidas funcionó para mí. Terminé simplemente extendiendo la interfaz de solicitud:

import {Request} from 'express';

export interface RequestCustom extends Request
{
    property: string;
}

Entonces para usarlo:

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

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

Editar : las versiones recientes de TypeScript se quejan de esto. En cambio, tuve que hacer:

someMiddleware(expressRequest: Request, res: Response, next: NextFunction): void
{
    const req = expressRequest as RequestCustom;
    req.property = '';
}
Tom Mettam
fuente
1
eso funcionará, pero bastante detallado si tiene cientos de funciones de middleware, amirite
Alexander Mills
1
@ user2473015 Sí, las versiones recientes de Typecript rompieron esto. Vea mi respuesta actualizada.
Tom Mettam
8

En TypeScript, las interfaces son abiertas. Eso significa que puede agregarles propiedades desde cualquier lugar con solo redefinirlas.

Teniendo en cuenta que está utilizando este archivo express.d.ts , debería poder redefinir la interfaz de solicitud para agregar el campo adicional.

interface Request {
  property: string;
}

Luego, en su función de middleware, el parámetro req también debería tener esta propiedad. Debería poder usarlo sin ningún cambio en su código.

toskv
fuente
1
¿Cómo "comparte" esa información a lo largo de su código? Si defino una propiedad en Solicitud, diga Request.user = {};en app.ts¿cómo la userController.tsconozco?
Nepoxx
2
@Nepoxx si redefine una interfaz, el compilador fusionará las propiedades y las hará visibles en todas partes, por eso. Idealmente, haría la redefinición en un archivo .d.ts. :)
toskv
Eso parece funcionar, sin embargo, si uso el tipo express.Handler(en lugar de especificar manualmente (req: express.Request, res: express.Response, next: express.NextFunction) => any)), no parece referirse a lo mismo, Requestya que se queja de que mi propiedad no existe.
Nepoxx
No lo esperaría, a menos que express.Handler amplíe la interfaz de solicitud. ¿lo hace?
toskv
2
Puedo hacer que eso funcione si lo uso, declare module "express"pero no si lo uso declare namespace Express. Prefiero usar la sintaxis del espacio de nombres, pero no me funciona.
WillyC
5

Si bien esta es una pregunta muy antigua, me encontré con este problema últimamente. La respuesta aceptada funciona bien, pero necesitaba agregar una interfaz personalizada Request, una interfaz que había estado usando en mi código y que no funcionaba tan bien con la aceptada. responder. Lógicamente, probé esto:

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

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

Pero eso no funcionó porque Typecript trata los .d.tsarchivos como importaciones globales y cuando tienen importaciones se tratan como módulos normales. Es por eso que el código anterior no funciona en una configuración de mecanografiado estándar.

Esto es lo que terminé haciendo

// typings/common.d.ts

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

export interface ITenant {
    ...
}
16 KB
fuente
Esto funciona para mi archivo principal, pero no en mis archivos de enrutamiento o controladores, no obtengo pelusa, pero cuando trato de compilar dice "La propiedad 'usuario' no existe en el tipo 'Solicitud'". (Estoy usando user en lugar de tenant), pero si agrego // @ ts-ignore encima de ellos, entonces funciona (aunque esa es una forma tonta de solucionarlo, por supuesto. ¿Tiene alguna idea sobre por qué puede que no sea así? trabajando para mis otros archivos?
Logan
Eso es algo muy extraño @Logan. ¿Puede compartir tu .d.ts, tsconfig.jsony la instancia de uso? Además, ¿qué versión de mecanografiado está utilizando ya que esta importación en módulos globales solo es compatible a partir de TS 2.9? Eso podría ayudar mejor.
16kb
He subido datos aquí, pastebin.com/0npmR1Zr No estoy seguro de por qué el resaltado está desordenado, aunque Esto es del archivo principal prnt.sc/n6xsyl Esto es de otro archivo prnt.sc/n6xtp0 Claramente, una parte de entiende lo que está pasando, pero el compilador no. Estoy usando la versión 3.2.2 de mecanografiado
Logan
1
Sorprendentemente, ... "include": [ "src/**/*" ] ...funciona para mí pero "include": ["./src/", "./src/Types/*.d.ts"],no lo hace. Todavía no me he dedicado a tratar de entender esto
16kb
Importar interfaz mediante importaciones dinámicas funciona para mí. Gracias
Roman Mahotskyi
3

Tal vez este problema haya sido respondido, pero quiero compartir solo un poco, ahora a veces una interfaz como otras respuestas puede ser un poco demasiado restrictiva, pero en realidad podemos mantener las propiedades requeridas y luego agregar propiedades adicionales para agregar mediante la creación de un clave con un tipo de stringcon el 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();
});

Entonces, ahora también podemos agregar cualquier propiedad adicional que queramos a este objeto.

Eka putra
fuente
2

Si está buscando una solución que funcione con express4, aquí está:

@ types / express / index.d.ts: -------- debe 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
fuente
2

Todas estas respuestas parecen estar equivocadas o desactualizadas de una forma u otra.

Esto funcionó para mí en mayo de 2020:

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

import * as express from "express"

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

en tsconfig.json, agregue / fusione la propiedad de modo que:

"typeRoots": [ "@types" ]

Salud.

Will Brickner
fuente
Funciona con Webpack + Docker, la importación * se puede reemplazar con la exportación {};
Dooomel
1

Una posible solución es utilizar "fundición doble para cualquier"

1- define una interfaz con tu propiedad

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

2- lanzamiento doble

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

Las ventajas de la doble colada son las siguientes:

  • tipings está disponible
  • no contamina las definiciones existentes sino que las amplía, evitando confusiones
  • como el casting es explícito, recopila multas con la -noImplicitanybandera

Alternativamente, existe la ruta rápida (sin escribir):

 req['myProperty'] = setProperty()

(no edite los archivos de definición existentes con sus propias propiedades; esto no se puede mantener. Si las definiciones son incorrectas, abra una solicitud de extracción)

EDITAR

Vea el comentario a continuación, el casting simple funciona en este caso req as MyRequest

Bruno Grieder
fuente
@akshay En este caso sí, porque MyRequestextiende el http.IncomingMessage. Si no fuera el caso, el doble casting vía anysería la única alternativa
Bruno Grieder
Se recomienda que lances a desconocido en lugar de a cualquiera.
dev
0

Esta respuesta será beneficiosa para aquellos que confían en el paquete npm ts-node.

También estaba luchando con la misma preocupación de extender el objeto de solicitud , seguí muchas respuestas en el desbordamiento de pila y terminé siguiendo la estrategia que se menciona a continuación.

Declaré escritura extendida para expreso en el siguiente directorio.${PROJECT_ROOT}/api/@types/express/index.d.ts

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

luego actualizo mi tsconfig.jsona algo como esto.

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

incluso después de realizar los pasos anteriores, Visual Studio dejó de quejarse, pero desafortunadamente, el ts-nodecompilador todavía solía lanzar.

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

Aparentemente, ts-nodeno pudo ubicar las definiciones de tipo extendido para la solicitud objeto de .

Finalmente, después de pasar horas, sabía que el Código VS no se quejaba y pude ubicar las definiciones de escritura, lo que implica que algo anda mal con el ts-nodecumplidor.

Actualización de inicio scripten package.jsonfijo por mí.

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

los --filesargumentos juegan un papel clave aquí para determinar las definiciones de tipos personalizados.

Para obtener más información, visite: https://github.com/TypeStrong/ts-node#help-my-types-are-missing

Divyanshu Rawat
fuente
0

Ayudar a cualquiera que esté buscando algo más para probar aquí es lo que funcionó para mí a fines de mayo de 2020 cuando intenté extender la Solicitud de ExpressJS. Tuve que haber probado más de una docena de cosas antes de que esto funcionara:

  • Cambie el orden de lo que todos recomiendan en el "typeRoots" de su tsconfig.json (y no olvide eliminar la ruta src si tiene una configuración rootDir en tsconfig como "./src"). Ejemplo:
"typeRoots": [
      "./node_modules/@types",
      "./your-custom-types-dir"
]
  • Ejemplo de extensión personalizada ('./your-custom-types-dir/express/index.d.ts "). Tuve que usar la importación en línea y las exportaciones predeterminadas para usar clases como un tipo en mi experiencia, por lo que también se muestra:
declare global {
  namespace Express {
    interface Request {
      customBasicProperty: string,
      customClassProperty: import("../path/to/CustomClass").default;
    }
  }
}
  • Actualice su archivo nodemon.json para agregar el comando "--files" a ts-node, ejemplo:
{
  "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 es verdad
fuente
0

Puede que ya sea bastante tarde para esta respuesta, pero de todos modos, así es como lo resolví:

  1. Asegúrese de tener su fuente de tipos incluida en su tsconfigarchivo (esto podría ser un hilo completamente nuevo)
  2. Dentro de su directorio de tipos, agregue un nuevo directorio y asígnele el nombre del paquete para el que desea extender o crear tipos. En este caso específico, creará un directorio con el nombreexpress
  3. Dentro del expressdirectorio, cree un archivo y asígnele un nombre index.d.ts(DEBE SER EXACTAMENTE ASÍ)
  4. Finalmente para hacer la extensión de los tipos solo necesitas poner un código como el siguiente:
declare module 'express' {
    export interface Request {
        property?: string;
    }
}
Hector manuel
fuente
-2

¿Por qué tenemos que hacer tantas molestias como en las respuestas aceptadas anteriores, cuando podemos hacer esto?

en lugar de adjuntar nuestra propiedad a la solicitud , podemos adjuntarla a los encabezados de la solicitud

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