325

I’m trying to add a property to express request object from a middleware using typescript. However I can’t figure out how to add extra properties to the object. I’d prefer to not use bracket notation if possible.

I’m looking for a solution that would allow me to write something similar to this (if possible):

app.use((req, res, next) => {
    req.property = setProperty(); 
    next();
});
Brainjump
  • 86
  • 7
Isak Ågren
  • 3,467
  • 2
  • 14
  • 15
  • 1
    you should be able to extend the request interface that the express.d.ts file provides with the fields you want. – toskv May 22 '16 at 19:17

30 Answers30

310

You want to create a custom definition, and use a feature in Typescript called Declaration Merging. This is commonly used, e.g. in method-override.

Create a file custom.d.ts and make sure to include it in your tsconfig.json's files-section if any. The contents can look as follows:

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

This will allow you to, at any point in your code, use something like this:

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)
})
16kb
  • 889
  • 9
  • 17
maximilianvp
  • 3,351
  • 1
  • 9
  • 5
  • 3
    I just did this, but I got it work without adding my custom.d.ts file to the files section in my tsconfig.json, yet it still works. Is this expected behavior? – Chaim Friedman May 17 '17 at 19:20
  • 1
    @ChaimFriedman Yes. The `files` section restricts the set of files included by TypeScript. If you don't specify `files` or `include`, then all `*.d.ts` are included by default, so there's no need to add your custom typings there. – interphx Jan 12 '18 at 08:02
  • 28
    Not working for me: I get `Property 'tenant` does not exist on type 'Request' ` Doesn't make a difference if I explicitly include it in `tsconfig.json` or not. **UPDATE** With `declare global` as @basarat pointet out in his answear works, but I had to do `import {Request} from 'express'` first. – Lion Feb 10 '18 at 19:24
  • 23
    FWIW, this answer is now _obsolete_. JCM's answer is the correct way to augment the `Request` object in expressjs (4.x at least) – Eric Liprandi Jul 25 '19 at 23:13
  • 7
    For future searches - a good example I found that worked out of the box: https://github.com/3mard/ts-node-example – jd291 Sep 19 '19 at 12:24
  • 2
    JCM's answer removed the existing properties from the Request object. The answer from kaleidawave worked instead – Dusty48 May 06 '20 at 23:10
  • This works with `string` but it doesn't work for a custom type :( – CodyBugstein Aug 02 '20 at 06:55
  • 1
    This answer seems to work reliably: https://stackoverflow.com/a/58788706/5157205 – pa1nd Nov 06 '20 at 19:21
  • 15
    @EricLiprandi Who is JCM and where is the answer you're referring to? Please link when referencing another answer. Names can change over time. – devklick Apr 03 '22 at 11:09
  • 3
    @devklick that is a very good question! I guess the answer has since been deleted... I have not dealt with that in a few years. But I would assume basarat's [answer](https://stackoverflow.com/a/47448486/80280) would be the authority. He is kind of a big deal ;) – Eric Liprandi Apr 04 '22 at 20:01
  • @interphx well, it seems the new typescript doesn't do that anymore. It doesn't automatically include the *.d.ts files. At lest, not for me – Ionel Lupu Sep 05 '22 at 20:31
218

As suggested by the comments in the index.d.ts, you simply declare to the global Express namespace any new members. Example:

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

Full Example:

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

More

Extending global namespaces is also covered in the TypeScript Deep Dive.

basarat
  • 261,912
  • 58
  • 460
  • 511
  • 4
    Why is global needed in the declaration? What happens if its not there? – Jason Kuhrt Jan 27 '18 at 02:29
  • This works with interfaces, but in case anyone needs to merge types, note that types are "closed" and cannot be merged: https://github.com/Microsoft/TypeScript/issues/24793#issuecomment-395820071 – Peter W Aug 15 '19 at 04:30
  • I also had to add to my tsconfig.json: { "compilerOptions": { "typeRoots": ["./src/typings/", "./node_modules/@types"] }, "files": [ "./src/typings/express/index.d.ts" ] } – marcellsimon Feb 21 '20 at 23:15
  • What I don't understand is the casing of Express. Why does it need to be Express instead of express? Is the way it works that the first letter of the library should be capitalized? – Iblisto Jan 25 '22 at 20:24
  • what if I have like 10-20 definition files? Do I have to include all of them manually? – Ionel Lupu Sep 05 '22 at 20:29
  • This worked for me, but I also had to change my definitions file to `_types/index.ts` (no .d.) and my tsconfig had typeRoots: ['_types']. I was importing another type into my definitions file, too. Just waiting for the house of cards to come crashing down any minute now... – Ben Steward Jan 25 '23 at 22:15
  • `context` is `undefined` before you assign `Context(..)`. So you want `interface Request { context?: Context }` instead of `interface Request { context: Context }` – Page not found Mar 19 '23 at 23:09
133

For newer versions of express, you need to augment the express-serve-static-core module.

This is needed because now the Express object comes from there: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/8fb0e959c2c7529b5fa4793a44b41b797ae671b9/types/express/index.d.ts#L19

Basically, use the following:

declare module 'express-serve-static-core' {
  interface Request {
    myField?: string
  }
  interface Response {
    myField?: string
  }
}
jonathancardoso
  • 11,737
  • 7
  • 53
  • 72
  • 3
    This worked for me, whereas extending the plain-old `'express'` module did not. Thank you! – Ben Kreeger May 06 '19 at 04:15
  • Thanks, I didn't know that express changed things about it and I didn't find a good way to specify my payload without an extended interface. – Vincent Jun 30 '19 at 22:01
  • 13
    Was struggling with this, in order to get this to work, I had to import the module as well: ```import {Express} from "express-serve-static-core";``` – andre_b Aug 10 '19 at 17:44
  • 3
    @andre_b Thanks for the hint. I think that the import statement turns the file into a module, and this is the part that's necessary. I've switched to using `export {}` which also works. – Danyal Aytekin Aug 22 '19 at 20:48
  • 3
    Make sure the file this code goes in is _not_ called `express.d.ts`, otherwise the compiler will try to merge this in to the express typings, resulting in errors. – Tom Spencer Oct 21 '19 at 15:46
  • 10
    Make sure your types must be first in typeRoots! types/express/index.d.ts and tsconfig => "typeRoots": ["./src/types", "./node_modules/@types"] – kaya Oct 23 '19 at 20:29
  • Did not work for me. I end up with `Parameter 'req' implicitly has an 'any' type.ts(7006)`. [This solution](https://stackoverflow.com/a/40762463/7484121) works perfectly fine. – NonPolynomial Jan 06 '20 at 16:58
  • 1
    @andre_b where did you import the module? In the `.d.ts` file, or in your consuming file? – Trojan Mar 19 '20 at 02:27
  • 1
    a bit of an incomplete answer here, can you elaborate on where this file is supposed to live? – shreddish Apr 22 '20 at 15:09
  • @shreddish this answer is based on the accepted one, you should create a declaration file (`.d.ts`) and add it to your tsconfig. https://stackoverflow.com/a/40762463/710693 – jonathancardoso Apr 22 '20 at 19:21
  • thank you, i tried almost all the other solutions and none worked. simple using the format you said worked without using any import. i think this is what works is the latest typescript version. – rakesht Jan 11 '21 at 14:43
  • Thank you so much. This should be the new official answer. – Antoine Jaussoin Aug 28 '21 at 18:42
  • I'm new to express but this worked wrapping it with `declare global { ... }`. And I'm not sure why it worked even when I didn't add the `.d.ts` file, in which I declared it, to my tsconfig. – August Feb 01 '22 at 08:31
  • i just augment the `express` module itself and its working.. the best part is, i dont know why its working – slier Mar 16 '22 at 10:02
  • what if I have like 10-20 definition files? Do I have to include all of them manually? – Ionel Lupu Sep 05 '22 at 20:28
  • Thank you so much @kaya. I didn't realize the order mattered before I saw your comment! – Olivier Lecrivain Mar 15 '23 at 09:29
92

After trying 8 or so answers and not having a success. I finally managed to get it working with jd291's comment pointing to 3mards repo.

Create a file in the base called types/express/index.d.ts. And in it write:

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

and include it in tsconfig.json with:

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

Then yourProperty should be accessible under every request:

import express from 'express';

const app = express();

app.get('*', (req, res) => {
    req.yourProperty = 
});
Ben
  • 3,160
  • 3
  • 17
  • 34
52

The accepted answer (as the others) does not works for me but

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

did. Hope that will help someone.

max-lt
  • 1,037
  • 10
  • 12
  • 2
    Similar method is described in [ts docs](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) under "Module Augmentation". It's great if you don't want to use `*.d.ts` files and just store your types within regular `*.ts` files. – im.pankratov Oct 29 '18 at 19:26
  • 3
    this is the only thing that worked for me as well, all other answers seem to need to be in .d.ts files – parliament Dec 15 '18 at 02:58
  • 1
    This works for me as well, provided that I put my `custom-declarations.d.ts` file in the TypeScript's project root. – focorner Nov 27 '19 at 14:29
  • 2
    I extended the original type to preserve it: `import { Request as IRequest } from 'express/index';` and `interface Request extends IRequest`. Also had to add the typeRoot – Ben Creasy Dec 07 '19 at 03:28
  • 1
    After trying every answer, this is the only one that worked for me. For now, i had to add it directly to my main file, i hope i'll find another way that will be cleaner. – millenion Jul 28 '21 at 00:19
  • you need to add `import express from 'express'` or else other symbol is completely undefined like `Response, NextFunction , ...` – slier Mar 16 '22 at 10:09
  • tried so many answers but this one worked fine. Thankyou! – ppxx Nov 05 '22 at 14:07
51

None of the offered solutions worked for me. I ended up simply extending the Request interface:

import {Request} from 'express';

export interface RequestCustom extends Request
{
    property: string;
}

Then to use it:

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

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

Edit: Depending on your tsconfig, you may need to do it this way instead:

someMiddleware(expressRequest: Request, res: Response, next: NextFunction): void
{
    const req = expressRequest as RequestCustom;
    req.property = '';
}
Tom Mettam
  • 2,903
  • 1
  • 27
  • 38
  • 7
    that will work, but quite verbose if you have 100s of middleware functions, amirite – Alexander Mills Feb 28 '18 at 03:01
  • 1
    As of 1.10.2020, it seems like creating a new interface that extends the Response/Request interface from Express itself, should work totally fine. I have `interface CustomResponse extends Response { customProperty: string}` and where I call it `someMiddleware(res: CustomResponse)` and it works fine, with access to properties that exist both on Response and the newly defined interface. – Petar Oct 01 '20 at 15:02
  • 2
    I prefer this method, its more explicit and clear than silently extending the request object somewhere behind the scenes. Makes it clear what properties are yours and what are from the source module – Paul Grimshaw Feb 14 '21 at 07:30
  • 5
    "Depending on your tsconfig" - depending on what property of the tsconfig? I want to change it accordingly to be able to use the interface solution. Why does this not work by default, seems a bit against the rules of OOP to me.. – TheProgrammer Jul 19 '21 at 10:49
  • `Req` is not being "modified" by the type. It's always the same object, the interface declaration just allows you to add additional properties to the object in a type-safe manner. – Tom Mettam Nov 04 '21 at 14:27
  • This does not work. when i add this to app.post(path, mddleware, handler) it gives error as middleware has no same structure – Yusuf Mar 09 '22 at 16:24
  • Yusuf, what is the exact error that you receive? – Tom Mettam Mar 12 '22 at 16:43
  • 7
    I think, @Yusuf and I got the same error: `Type '(req: CustomRequest, res: Response>) => Promise' is not assignable to type 'RequestHandler>'. Types of parameters 'req' and 'req' are incompatible.` – winklerrr Apr 29 '22 at 10:02
  • To solve the "Not assignable to type" error, you can simply make a wrapper/helper function: ```export function proxyAuthRequest( handler: (req: AuthRequest, res: Response) => void | Promise ) { return (req: Request, res: Response) => handler(req as AuthRequest, res); }``` Then use it to wrap the normal handler: ```server.get( Endpoint.Buildings, authController.authenticateJwt, authController.proxyAuthRequest(gameController.listBuildings) );``` – John Doe May 06 '22 at 01:12
  • Did you find any solution to the metione problem @winklerrr? I have faced the similar issue where my controller is crying about it. – aevin Mar 30 '23 at 09:09
  • @TomMettam, which configuration were you specifically talking about in the above answer which needed the second option? – aevin Mar 30 '23 at 09:44
35

In 2023 this one is working:

With express 4.17.1 the combination of https://stackoverflow.com/a/55718334/9321986 and https://stackoverflow.com/a/58788706/9321986 worked:

in types/express/index.d.ts:

declare module 'express-serve-static-core' {
    interface Request {
        task?: Task
    }
}

and in tsconfig.json:

{
    "compilerOptions": {
        "typeRoots": ["./types"]
    }
}
anli
  • 515
  • 6
  • 9
34

Alternative solution

This is not actually answering to the question directly, but I'm offering an alternative. I was struggling with the same problem and tried out pretty much every interface extending solution on this page and none of them worked.

That made me stop to think: "Why am I actually modifying the request object?".

Use response.locals

Express developers seem to have thought that users might want to add their own properties. That's why there is a locals object. The catch is, that it's not in the request but in the response object.

The response.locals object can contain any custom properties you might want to have, encapsulated in the request-response cycle, thus not exposed to other requests from different users.

Need to store an userId? Just set response.locals.userId = '123'. No need to struggle with the typings.

The downside of it is that you have to pass the response object around, but it's very likely that you are doing it already.

https://expressjs.com/en/api.html#res.locals

Typing

Another downside is the lack of type safety. You can, however, use the generic types on the Response object to define what's the structure of the body and the locals objects:

Response<MyResponseBody, MyResponseLocals>

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express/index.d.ts#L127

Caveats

You cannot really guarantee that the userId property is actually there. You might want to check before accessing it, especially in case of objects.

Using the example above to add an userId, we could have something like this:

interface MyResponseLocals {
  userId: string;
}

const userMiddleware = (
  request: Request,
  response: Response<MyResponseBody, MyResponseLocals>,
  next: NextFunction
) => {
  const userId: string = getUserId(request.cookies.myAuthTokenCookie);
  // Will nag if you try to assign something else than a string here
  response.locals.userId = userId;
  next();
};

router.get(
  '/path/to/somewhere',
  userMiddleware,
  (request: Request, response: Response<MyResponseBody, MyResponseLocals>) => {
    // userId will have string type instead of any
    const { userId } = response.locals;

    // You might want to check that it's actually there
    if (!userId) {
      throw Error('No userId!');
    }
    // Do more stuff
  }
);
Timo
  • 2,740
  • 1
  • 24
  • 22
  • 1
    A more notable downside of this is that `response.locals` remains untyped. Any value stored in it is `any`. – Martti Laine Feb 14 '21 at 10:19
  • 1
    That is very much true, but I'm happy to accept it as a trade off. – Timo Feb 15 '21 at 11:11
  • 1
    Since Request and Response are Genereics by definitions, since locals have been defined exactly for that, this should be the accepted answers. I desagree with Martti Laine, Response.locals should be strongly typed using this method. But you have to specify "interface MyResponseLocals extends Record{...}" to match the generic constraint – Romain TAILLANDIER Dec 21 '21 at 08:18
  • 1
    I disagree. res.locals are for exposing stuff to the client. RES is Client Context not Server Context. "This property is useful for exposing request-level information such as the request path name, authenticated user, user settings, and so on to templates rendered within the application." – MisterMonk May 04 '22 at 08:19
  • 1
    It does not solve the problem if your request object is altered by another middleware. For example if you use express-session then it adds 'session' property to your 'req' and then you are back at square 1 – Michael S May 29 '23 at 19:54
27

All of these responses seem to be wrong or outdated in some way or another.

This worked for me in May, 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, add / merge the property such that:

"typeRoots": [ "@types" ]

Cheers.

Will Brickner
  • 824
  • 11
  • 12
15

While this is a very old question, I stumbled upon this problem lately.The accepted answer works okay but I needed to add a custom interface to Request - an interface I had been using in my code and that didn't work so well with the accepted answer. Logically, I tried this:

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

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

But that didn't work because Typescript treats .d.ts files as global imports and when they have imports in them they are treated as normal modules. That is why the code above doesn't work on a standard typescript setting.

Here's what I ended up doing

// typings/common.d.ts

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

export interface ITenant {
    ...
}
16kb
  • 889
  • 9
  • 17
  • This works for my main file, but not in my routing files or controllers, I get no linting, but when I try to compile it says "Property 'user' does not exist on type 'Request'." (I'm using user instead of tenant), but if I add // @ts-ignore above them, then it works (though that's a silly way to fix it of course. Do you have any thoughts on why it may not be working for my other files? – Logan Apr 02 '19 at 08:10
  • That is a very strange thing @Logan. Can you share your `.d.ts`, `tsconfig.json` and the use instance? Plus, what version of typescript are you using as this importing in global modules is only supported starting from TS 2.9? That could help better. – 16kb Apr 02 '19 at 15:22
  • I've uploaded data here, https://pastebin.com/0npmR1Zr I'm not sure why the highlighting is all messed up though This is from the main file https://prnt.sc/n6xsyl This is from another file https://prnt.sc/n6xtp0 Clearly some part of it understands what's going on, but the compiler does not. I'm using version 3.2.2 of typescript – Logan Apr 03 '19 at 13:38
  • 1
    Suprisingly, `... "include": [ "src/**/*" ] ...` Works for me but `"include": ["./src/", "./src/Types/*.d.ts"],` doesn't. I haven't gone in dept in trying to understand this yet – 16kb Apr 04 '19 at 12:05
  • Importing interface by using dynamic imports works for me. Thanks – Roman Mahotskyi Mar 29 '20 at 03:01
  • Had the same issue. That works but dynamic import like this seems dirty to me, isn't there a better way anyone knows about ? – Sylver Jul 24 '20 at 09:58
14

If you are looking for solution that works with express4, here it is:

@types/express/index.d.ts: --------must be /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 from https://github.com/TypeStrong/ts-node/issues/715#issuecomment-526757308

Chandara Chea
  • 289
  • 3
  • 11
12

This is what worked for me when using Nestjs and Express. As on Nov 2020.

Create a file: ./@types/express-serve-static-core/index.d.ts

Note: must have exactly the above path and file name. So that Typescript declaration merging will work.

import { UserModel } from "../../src/user/user.model";

declare global{
    namespace Express {
        interface Request {
            currentUser: UserModel
        }
    }
}

add this to your tsconfig.json

"typeRoots": [
      "@types",
      "./node_modules/@types",
    ]        
David Dehghan
  • 22,159
  • 10
  • 107
  • 95
  • 2
    For some reason, only your solution _almost_ worked for me. It's just that it wouldn't work unless I declare `Express` directly without `global`. – Danry Nov 15 '21 at 06:35
  • @Danry `declare global` is only needed when you import any modules in the same `*.d.ts` file – Kay Feb 10 '22 at 05:14
  • amazing that no solution "just works" – Bersan Oct 23 '22 at 12:10
11

This answer will be beneficial to those who rely on npm package ts-node.

I was also struggling with the same concern of extending request object, I followed a lot of answers in stack-overflow & ended with following the below-mentioned strategy.

I declared extended typing for express in the following directory. ${PROJECT_ROOT}/api/@types/express/index.d.ts

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

then updating my tsconfig.json to something like this.

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

even after doing the above steps, the visual studio stopped complaining, but unfortunately, the ts-node compiler still used to throw.

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

Apparently, the ts-node was not able to locate the extended type definitions for request object.

Eventually after spending hours, as I knew the VS Code was not complaining & was able to locate the typing definitions, implying something is wrong with ts-node complier.

Updating start script in package.json fixed it for me.

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

the --files arguments play a key role here find determining the custom type definitions.

For further information please visit: https://github.com/TypeStrong/ts-node#help-my-types-are-missing

Divyanshu Rawat
  • 4,421
  • 2
  • 37
  • 53
10

In TypeScript, interfaces are open ended. That means you can add properties to them from anywhere just by redefining them.

Considering that you are using this express.d.ts file, you should be able to redefine the Request interface to add the extra field.

interface Request {
  property: string;
}

Then in your middleware function, the req parameter should have this property as well. You should be able to use it without any changes to your code.

Mouneer
  • 12,827
  • 2
  • 35
  • 45
toskv
  • 30,680
  • 7
  • 72
  • 74
  • 1
    How do you "share" that information throughout your code? If I define a property in Request, say `Request.user = {};` in `app.ts` how does `userController.ts` know about it? – Nepoxx Aug 04 '16 at 13:41
  • 2
    @Nepoxx if you redefine an interface the compiler will merge the properties and make them visible everywhere, that's why. Ideally you'd do the redefinition in a .d.ts file. :) – toskv Aug 04 '16 at 13:43
  • 1
    That seems to work, however if I use the type `express.Handler` ( instead of manually specifying `(req: express.Request, res: express.Response, next: express.NextFunction) => any)`), it does not seem to refer to the same `Request` as it complains that my property does not exist. – Nepoxx Aug 04 '16 at 14:01
  • I wouldn't expect it to, unless express.Handler extends the Request interface. does it? – toskv Aug 04 '16 at 14:03
  • Where do you put that new `interface` definition? I've tried creating a file with it in it and including it but it just complains I have two things called "Request"; it doesn't just combine the two. I understand why I'm getting the error but this answer promises me some simple magic and I can't make it happen. – WillyC Nov 29 '16 at 17:41
  • @WillyC have you tried maximilianvp's answer.. it has a more complete example for this particular case. :) – toskv Nov 29 '16 at 19:07
  • 2
    I can make that work if I use `declare module "express"` but not if I use `declare namespace Express`. I'd rather use the namespace syntax but it just doesn't work for me. – WillyC Dec 01 '16 at 21:28
10

I solved this problem by creating a new type without extending the Request type globally.

import { Request } from 'express'
    
type CustomRequest = Request & { userId?: string }

You must use extend properties with optional(?) operator to not have 'No overload matches this call' error.

Package Versions :

    "@types/express": "^4.17.13",
    "@types/morgan": "^1.9.3",
    "@types/node": "^17.0.29",
    "typescript": "^4.6.3",
    "express": "^4.18.0",
erdemgonul
  • 125
  • 1
  • 9
5

One possible solution is to use "double casting to any"

1- define an interface with your property

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

2- double 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()
})

The advantages of double casting are that:

  • typings is available
  • it does not pollute existing definitions but extends them, avoiding confusion
  • since the casting is explicit, it compiles fines with the -noImplicitany flag

Alternatively, there is the quick (untyped) route:

 req['myProperty'] = setProperty()

(do not edit existing definition files with your own properties - this is unmaintainable. If the definitions are wrong, open a pull request)

EDIT

See comment below, simple casting works in this case req as MyRequest

Bruno Grieder
  • 28,128
  • 8
  • 69
  • 101
  • @akshay In this case, yes, because `MyRequest` extends the `http.IncomingMessage`. It it were not the case, double casting via `any` would be the only alternative – Bruno Grieder Jun 30 '16 at 12:50
  • It is recommended that you cast to unknown instead of any. – dev May 04 '20 at 13:51
  • Casting like this unfortunately requires the same repeated casting in every subsequent function in the chain. For example middleware1, middleware2, middleware3, AND the route itself. But this is the only implementation I've found that implements context-relevant typings, rather than just globally extending `Request` and putting every possible property on that. – defraggled Nov 06 '21 at 07:05
5

Maybe this issue has been answered, but I want to share just a little, now sometimes an interface like other answers can be a little too restrictive, but we can actually maintain the required properties and then add any additional properties to be added by creating a key with a type of string with value type of any

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

So now, we can also add any additional property that we want to this object.

Eka putra
  • 739
  • 10
  • 15
5

To help anyone who is just looking for something else to try here is what worked for me in late May of 2020 when trying to extend ExpressJS' Request. I had to have tried more than a dozen things before getting this to work:

  • Flip the order of what everyone is recommending in the "typeRoots" of your tsconfig.json (and don't forget to drop the src pathing if you have a rootDir setting in tsconfig such as "./src"). Example:
"typeRoots": [
      "./node_modules/@types",
      "./your-custom-types-dir"
]
  • Example of custom extension ('./your-custom-types-dir/express/index.d.ts"). I had to use inline import and default exports to use classes as a type in my experience so that is shown too:
declare global {
  namespace Express {
    interface Request {
      customBasicProperty: string,
      customClassProperty: import("../path/to/CustomClass").default;
    }
  }
}
  • Update your nodemon.json file to add the "--files" command to ts-node, example:
{
  "restartable": "rs",
  "ignore": [".git", "node_modules/**/node_modules"],
  "verbose": true,
  "exec": "ts-node --files",
  "watch": ["src/"],
  "env": {
    "NODE_ENV": "development"
  },
  "ext": "js,json,ts"
}
IfTrue
  • 489
  • 8
  • 25
5

If you tried all the answers and still didn't get it to work, here is a simple hack

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

This will cast the req object to any, therefore you can add any property you want. Keep in mind that by doing this you will lose type safety with req object.

Idrees
  • 97
  • 1
  • 1
5

I have same problem and resolve it like this:

// /src/types/types.express.d.ts
declare namespace Express {
    export interface Request {
        user: IUser
    }
}

But some conditions are required!

  1. Add to tsconfig.json config
"paths": {
    "*": [
        "node_modules/*",
        "src/types/*"
    ]
},

After this tsc will build bundle, but ts-node not.

  1. You must add --files to compiler command
ts-node --files src/server.ts
MiF
  • 638
  • 7
  • 13
  • 1
    This worked for me, except for second part - I added the paths to the the typeRoots property of my tsconfig file. `"typeRoots": [ "./node_modules/*", "./src/types/*" ]` – TJBlackman Mar 29 '22 at 21:04
2

Simple solution which worked for me is to create a new custom interface extending express Request. This interface should contain all your custom req properties as optional. Set this interface as type for the middleware req.

// ICustomRequset.ts
   import { Request } from "express"
   export default interface ICustomRequset extends Request {
       email?: string;
       roles?: Array<string>;
   }

// AuthMiddleware.ts
...
export default async function (
  req: ICustomRequset,
  res: Response,
  next: NextFunction
) {
  try {
      // jwt code
      req.email=jwt.email
      req.roles=jwt.roles
      next()
  }catch(err){}
  • This question is about the addition of custom properties to existing request interface that can only be done using type declaration files. – Abhishek Pankar Mar 22 '21 at 09:57
  • 1
    @AbhishekPankar why would you say that extension can only be done using type declaration files? @AshiSultan 's implementation looks fine to me. Admittedly I can't get it to work though. Typescript doesn't like when this middleware is applied on the final route.`No overload matches this call` – defraggled Nov 04 '21 at 11:03
  • @defraggled What I meant was without using external interfaces, type declaration is the only solution – Abhishek Pankar May 19 '23 at 17:45
2

I used response.locals object to store the new property. Here is the complete code

export function testmiddleware(req: Request, res: Response, next: NextFunction) {
    res.locals.user = 1;
    next();
}

// Simple Get
router.get('/', testmiddleware, async (req: Request, res: Response) => {
    console.log(res.locals.user);
    res.status(200).send({ message: `Success! ${res.locals.user}` });
});
sreejith v s
  • 1,334
  • 10
  • 17
1

It might be already quite late for this answer, but anyway here is how I solved:

  1. Make sure you have your types source included in your tsconfig file (this could be a whole new thread)
  2. Inside your types directory add a new directory and name it as the package you want to extend or create types for. In this specific case, you will create a directory with the name express
  3. Inside the express directory create a file and name it index.d.ts (MUST BE EXACTLY LIKE THAT)
  4. Finally to make the extension of the types you just need to put a code like the following:
declare module 'express' {
    export interface Request {
        property?: string;
    }
}
Hector Manuel
  • 484
  • 5
  • 10
1

on mac with node 12.19.0 and express 4, using Passport for auth, I needed to extend my Session object

similar as above, but slightly different:

import { Request } from "express";


declare global {
  namespace Express {
    export interface Session {
      passport: any;
      participantIds: any;
      uniqueId: string;
    }
    export interface Request {
      session: Session;
    }
  }
}

export interface Context {
  req: Request;
  user?: any;
}```
user3325025
  • 654
  • 7
  • 10
1

The easiest method is to extend the type you want and add your own properties

in tsconfig.ts specify the root of local types

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

now create any .d.ts file inside @types, you can put @types in the root or anyware.

@types/express.d.ts

declare namespace Express {
  interface Request {
    // add arbitrary keys to the request
    [key: string]: any;
  }
}
Sh eldeeb
  • 1,589
  • 3
  • 18
  • 41
1

Solution which finally worked for me with typescript 4.8.4 and express 4.18.2:

Taking this COMMENT and wrapping the whole thing in "declare global" like this:

declare global {
  declare module 'express-serve-static-core' {
    interface Request {
      userId?: string;
    }
  }
}

File structure:

/typeDeclarations/express/index.d.ts
/tsconfig.json

I have also added path to my declarations to the tsconfig file, but everything also worked without it.

  "typeRoots": [
    "./node_modules/@types",
    "./typeDeclarations"
  ],  
mihi321
  • 36
  • 4
0

I recall my below answer as obsolete

This worked for me:

declare namespace e {
    export interface Request extends express.Request {
        user:IUserReference,
        [name:string]:any;
    }
    export interface Response extends express.Response {
        [name:string]:any;
    }
}



export type AsyncRequestHandler = (req:e.Request, res:e.Response, logger?:Logger) => Promise<any>|Promise<void>|void;
export type AsyncHandlerWrapper = (req:e.Request, res:e.Response) => Promise<void>;

And I used it in code like exporting a function with such signatures in such way:

app.post('some/api/route', asyncHandlers(async (req, res) => {
        return await serviceObject.someMethod(req.user, {
            param1: req.body.param1,
            paramN: req.body.paramN,
            ///....
        });
    }));

What to use instead of "declarations merging"

These options are possible:

  • make functions that get context property/functionality for request (A)
  • make single function to get fully custom context from request (B). The option for specialized context function is similar to
async function getRequestUser(req:Request):Promise<ICustomUser> {
   let currentUser:ICustomUser = req[UserSymbolProp] || null as ICustomUser;
   // if user not set already, try load it, if no such user but header present, throw error?
   return currentUser;
} 
app.get('/api/get/something', async(req, res, next) => {
   try {
      let user = await getRequestUser(req);
      //do something else
   } catch(err) {
     next(err);
   }
});

Other option is quite similar, but you make single function that returns all custom context you need in your code:

function getAPIContext(req:Request):IAPIContext {
   let ctx = req[APICtxSymbol] || null as IApiContext;
   if (!ctx) {
     ctx = prepareAPIContext(req);
     req[APICtxSymbol] = ctx;
   }
   return ctx;
}

app.get('/api/to/get/something', async(req, res, next) => {
   try {
      let ctx = getAPIContext(req);
      ///use context somehow
      let reply = await doSomething(ctx);
      res.json(reply);
   } catch(err) {
      next(err);
   }
}

Second approach is better, as you can create unit tests that use test-special implementation of context and make unit tests directly over doSomething (export that code, of course). Second construction can be reused via function like wrapHandler that accept real handling function like

function wrapHandler<T>(handler: (req:IAPIContext) => Promise<T>|T) => (req:Request, res:Response, next:NextFunction) => Promise<void>|void;
Kote Isaev
  • 273
  • 4
  • 13
  • Is there any purpose to your `AsyncRequestHandler` and `AsyncHandlerWrapper` types? They are declared but not used in your example – devklick Apr 03 '22 at 11:26
  • Well, these types a part of API where I use the extended requests, the `asyncHandlers` so I copied these types as part of usage context, to make sure the req and res are considered as express Request and Response types, not DOM fetch Request and Response types. if you use raw express, specify the types of `req` and `res` explicitly in handler function parameters declaration to make sure interface merging will work as expected – Kote Isaev Apr 04 '22 at 18:19
  • 1
    What's the difference between namespace `e` and namespace `Express`? Because in DefinitelyTyped, it seems that `@types/express` uses `e`, whereas `@types/express-serve-static-core` uses `Express`, but I can't figure out what the difference is exactly. – damd Aug 15 '23 at 22:01
0

For simple case, I use the headers property in outer middleware and get it later in inner middleware.

// outer middleware
req.headers["custom_id"] = "abcxyz123";

// inner middleware
req.get("custom_id");

drawbacks:

  • can only store string. if you want to store other types such as json or number, you might have to parse it later.
  • the headers property is not documented. Express only documents the req.get() method. So you have to use the exact version of Express that works with property headers.
irous
  • 401
  • 3
  • 8
-2

Solving this with .d.ts declarations are hacks. Accept the fact that express' middleware system is not for typescript. I suggest not to use them.

If you use .d.ts, you lose compile time checks. You might expect that something is there, even if it is not. Or you can define it as optional, but then you have to check every time whether it is there or not. We use typescript to catch the errors at compile time, but .d.ts files doesn't help us. They can't be a solution.

Example express middleware (NOT recommended):

const auth = (req, res) => {
  const user = // parse user from the header

  if(!user)
     return res.status(401).send({ result: 'unauthorized-error' })

  req.user = user
  return next()
}

app.get('/something', auth, (req, res) => {
  // do something
})

Better code:

const auth = (req) => {
  return // parse user from the header
}

app.get('/something', (req, res) => {
  const user = auth(req)
  if(!user)
    return res.status(401).send({ result: 'unauthorized-error' })
  // do something
})

You can get your middleware like usage back with higher order functions:

const auth = (req) => {
    return // parse user from the header
}

const withUser = (callback: (foo, req, res) => void) => (req, res) => {
    const user = auth(req)
    if(!user)
        return res.status(401).send({ result: 'unauthorized-error' })
    return callback(user, req, res)
}

app.get('/something', withUser((user, req, res) => {
    // do something
}))

You can even stack them up if you want:

const withFoo = (callback) => (req, res) => { /* ... */ }
const withBar = (callback) => (req, res) => { /* ... */ }
const withBaz = (callback) => (req, res) => { /* ... */ }

const withFooBarAndBaz = (callback) => (req,res) => {
    withFoo((foo) =>
        withBar((bar) =>
            withBaz((baz) =>
                callback({ foo, bar, baz }, req, res)
            )(req,res)
        )(req,res)
    )(req,res)
}

app.get('/something', withFooBarAndBaz(({ foo, bar, baz }, req, res) => {
    // do something with foo, bar and baz
}))

You can use the language instead of unsafe mutations that express promotes.

Before my current solution, I used the first proposed way to achieve middlewares, but with the difference that my auth function threw an error, which I could catch and return a correct response, so I didn't have to do it in the controller. For example:

app.get('/something', withResponse((req) => {
   const user = auth(req)
   return success({
     message: `Hi ${user.name}`
   })
}))

I also returned the responses instead of manually calling res.send. It allowed me to type the responses as well.

My current solution goes much further, where I can auto infer every return type which I can use on the frontend immediately. For this, Check tRPC.

If you want to keep your REST-like API structure, with extra type-safety guarantee, check out my tRPC alternative: Cuple RPC

Dávid Bíró
  • 336
  • 3
  • 6
  • I guess your signature is wrong `(callback: (foo, req, res) => void) => (req, res)`. `withUser` doesn't return `(req, res)`. – Shuzheng Oct 14 '22 at 12:30
  • You must have misread. That's a function declaration starting with (req, res) => {} which is a valid express controller. It's a function in function. – Dávid Bíró Oct 15 '22 at 19:34
  • nested higher order functions are a code smell imo - you'll run into callback hell and really long indented lines of junk that is hard to breakpoint debug. this is why we have dependency injection these days (which is pretty bulky). If you want to code like this (which is somewhat ok, but not suited for js) - go try .NET Core. – PathToLife Oct 19 '22 at 13:43
  • @DávidBíró - Thank you. So, `(callback: (foo, req, res) => void) => (req, res) => {}` should be read as `(callback: (foo, req, res) => void) => ((req, res) => {})` ? I.e. it's a function taking `callback` and returns a function `(req, res) => {}`? – Shuzheng Oct 22 '22 at 09:47
  • @Shuzheng Yes, that's the way. – Dávid Bíró Oct 25 '22 at 22:08
  • @PathToLife I agree, but it's easy for the typesystem. I personally use this proposed solution, and this callback hell is unlikely. Usually we only have one auth middleware. That's all. I showed these variants just in case. DI has its own problems, I personally like to avoid it as I think it's easier to implement new features without it. (If I was allowed to replace js I would do it in the first place.) – Dávid Bíró Oct 25 '22 at 22:08
  • EDIT: To make it clear, I use the first proposed solution. The withStyle was an idea that you can use it like a "middleware" if you want. – Dávid Bíró Oct 25 '22 at 22:18
  • @DávidBíró I took a look at tRPC, and that gif example at website gives me feel like all server-side stuff and Node declarations from `@trpc/server` will be bundled into client code with `@trpc/server` while importing `appRouter` – Kote Isaev Aug 16 '23 at 02:21
  • @KoteIsaev Your fear is real. Use "import type" instead of normal "import" as suggested, and make sure you import only the required type. However normal "import" will work as well because if you import only the type there will be no difference in the built files, however, if you use "import type" it's an extra safety as you are unable to import non-types with it. – Dávid Bíró Aug 20 '23 at 22:58
-5

Simply add the property to req.params object.

req.params.foo = "bar"
Milipp
  • 31
  • 3
  • 2
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 26 '21 at 14:06