Extend Express's Request Object with Typescript Declaration Merging.
Kwabena Bio Berko

Kwabena Bio Berko @kwabenberko

About: Software Engineer

Location:
Accra, Ghana
Joined:
Oct 16, 2017

Extend Express's Request Object with Typescript Declaration Merging.

Publish Date: Jan 29 '20
78 45

Express has been my go to server side node web framework for the past few years. Its fast, unopinionated and so easy to get up and running. I really love using it along with Typescript too, I should say. It enhances code quality and understand-ability. Refactoring your code in Typescript is also much more easier and faster. Plus, you get the added advantage of code completion and IntelliSense when using modern text editors like Visual Studio Code. 😋

One of the concepts of Typescript which I've recently began using is Declaration Merging.
Declaration Merging allows you to merge two or more distinct declaration or types declared with the same name into a single definition. This concept allows you to attach your own custom property onto another Typescript interface type. Lets take a look at a typical Express middleware.

Alt Text

The above code is an Express middleware that is used to ensure that a user is authenticated when he or she tries to access a protected resource. It decodes the user's token from the authorization property of the request headers and attaches the user to the Request object. But see that red squiggly line?
Thats because the property currentUser does not exist on Express's Request interface type. Let's fix that. 😃

The first thing we need to do is to create a new declaration file @types > express > index.d.ts in the root of our project.

You would notice this is the exact same file name and path in our node_modules/@types folder. For Typescript declaration merging to work, the file name and its path must match the original declaration file and path.

Next we need to make some few changes in the project's tsconfig.json file. Let's update the typeRoots value to the following:

...
"typeRoots": [
      "@types",
      "./node_modules/@types",
    ]                    
...
Enter fullscreen mode Exit fullscreen mode

By default, the Typescript compiler looks for type definitions in the node_modules/@types folder. The above code instructs the compiler to look for type definitions in this folder as well as our custom @types folder in our project root.

It's now time to add our custom currentUser property to Express's Request interface type by modifying the index.d.ts file we created earlier:


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

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

Enter fullscreen mode Exit fullscreen mode

Lets take a look again at our middleware file and we immediately notice that the red squiggly line is gone! This is because the Typescript compiler now recognizes the currentUser property as a valid property on the Request type interface.

Alt Text

Happy Coding, everyone!

Comments 45 total

  • terabytes
    terabytesMar 2, 2020

    VS code doesn't show error in editor but during compilation i get:
    currentUser does not exist on type 'Request'.
    Its Ts error.

    • Mehmet N Yarar
      Mehmet N YararJul 9, 2020

      same here. not only that, testing (jest/ts-jest) also fails unless I do:

      declare module 'express' {
        export interface Request {
          i18n: import('i18next').i18n
          language: string
        }
      }
      
    • Beadle
      BeadleFeb 11, 2022

      I am having the same issue. Did you every find a fix for this?

  • TJSarkka
    TJSarkkaMar 8, 2020

    Thank you! I spent the last hours scratching my head and trying different combinations of config files and .d.ts files provided in other solutions.. and this was the only one that worked for me for both ts-node and tsc. :)

  • Qi Mu
    Qi MuApr 24, 2020

    Thank you so much!!!

  • Mark Tsai
    Mark TsaiMay 2, 2020

    Similar methods to define Request types can also be found in the following libraries:

    I also spent so much time resolving this issue, thank you for your article!

  • Rene Maldonado Fimbres
    Rene Maldonado FimbresMay 16, 2020

    Bro, have been having problems with this for 2 days.

    Thanks for the info, I was missing the @ sign .

  • Anderson Luis Silva
    Anderson Luis SilvaAug 8, 2020

    Thank you very much!
    Very well explained.

  • riejoc16
    riejoc16Aug 27, 2020

    Oh wow! I was stuck for a while now trying to achieve exactly that. I couldn't find what I had done wrong. Only with this post I was able to get it done, thanks!

  • George C. Norris
    George C. NorrisSep 17, 2020

    Getting a Augmentations for the global scope can only be directly nested in external modules or ambient module declarations. ??

  • George C. Norris
    George C. NorrisSep 17, 2020

    I've tried SOO many variations to make this work, and NOTHING seems to work. 🤷🏻‍♂️

  • jpolstre
    jpolstreSep 23, 2020

    Thanks, in my case work whitout: declare global {

  • Asgamboa12
    Asgamboa12Oct 24, 2020

    Thank you very much, this problem was killing me and I could not find a way to solve it until I saw your post, thank you very much !!!

  • Adil
    AdilNov 19, 2020

    Thank you, it helped. But as far as I understood, it adds currentUser field to all Requests? What if I want it to be present only in the current file and not declared in the other files where I use Request?

    • Luca S. Martins
      Luca S. MartinsApr 1, 2022

      You could declare currentUser as optional and use it when needed

  • Ioannis
    IoannisNov 29, 2020

    Could you add a GitHub link please. It does not work for me as well.

  • Pendar
    PendarDec 10, 2020

    This was awesome! I was missing the step with having to create the same folder path structure and adding the "typeRoots"

  • Sakari Ellonen
    Sakari EllonenDec 21, 2020

    I created an account here just to say thank you, this was such a nice solution that a typescript newbie like me would not have come up on my own.

  • aashishClikFin
    aashishClikFinJan 13, 2021

    First of all thanks a lot for your post, I finally resolved the problem I faced since the last 3 months.

    Just would like to add one more bit, since I was using passport alongside passport-jwt hence your solution didn't exactly work for me.

    Here is what I used in the index.d.ts file

    declare module 'express-serve-static-core' {
    export interface Request {
    user?: yourCustomType;
    }
    }

    It might help someone else as well, but thanks a lot for guiding me in the right direction.

  • Diego CS
    Diego CSFeb 24, 2021

    Thanks!!!!!!!! I was looking for information, but all that I found was not useful, but your explanation goes straight to the point. Than you very much.

  • damjuve
    damjuveMar 5, 2021

    Hello,

    Thanks for this very usefull tips.
    I still have a question about exporting this.
    I would like to extend express Request with UserModel, exactly like you did. But I am doing it in a homemade node module.
    Then I install this node module in a node project and I don't have access to the extended request. How can i do this ?

  • Vitalii
    VitaliiMar 23, 2021

    Nice article! You saved my day 😎👍

    I see a lot of libraries using this approach.

    But It looks a bit strange for me. Just imagine that you have a lot of middleware that attaches something to the request object. In the end, you have a bloated interface and you lose the power of TypeScript because you don't know which middlewares were executed before handler/controller. So you don't know if the required data was attached and need to double-check if it's there 🤷‍♂️.

  • tukuyoma
    tukuyomaMay 7, 2021

    thanks ...this helped alot

  • saurav kumar  bhagat
    saurav kumar bhagatMay 18, 2021

    What if we directly extends the Request interface like:
    export interface CustomReq extends Request{
    currentUser: User
    }

    and use this instead?

  • Gabriel
    GabrielJun 25, 2021

    NOTE that you have to add typeRoots inside compilerOptions in the tsconfig.json file.

  • Peter Kitonga
    Peter KitongaAug 13, 2021

    If anyone is still having errors with this, don't forget to import Express at the top of your file.

    import { Express } from 'express'

    For some reason, I couldn't get it to work without the import

  • Dan Maia
    Dan MaiaSep 11, 2021

    The best article about that I've found today in the Internet.

  • playground
    playgroundFeb 9, 2022

    Great article, thanks for sharing. Good to know there is another way of doing things.
    I have been doing it the inverse by extending Request

    For example:
    export interface IRequest extends Request {
    type: string;
    sensorId: string;
    timestamp: ITime;
    }

    in the server.ts
    ...
    app.get("/test", (req: IRequest, res: IResponse) => {
    ...
    })

  • АнонимOct 5, 2022

    [deleted]

    • Kwabena Bio Berko
      Kwabena Bio BerkoOct 23, 2022

      Hi, @wardvisual, thanks for reading.
      The node_modules folder was not modified in this article. We created a new file, @types > express > index.d.ts in the root of our project, not in node_modules 🙂

  • Hassan
    HassanJan 25, 2023

    Thanks alot for the clear explanation,
    i've used your solution and it works berfictly with node ./dist/server.js and the compilation as well, however every time i use tsc watch i got the following error

    error TS2339: Property 'currentUser' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>.

  • Simon Texson
    Simon TexsonNov 25, 2024

    Thanks for this article.

    I had to import the Express and Request objects into the index.d.ts file like so:

    import Express, {Request} from 'express'
    import {UserModel} from 'path/to/user/model'
    
    declare global {
        namespace Express {
            interface Request {
                currentUser: UserModel;
            }
        }
    }
    
    Enter fullscreen mode Exit fullscreen mode

    Hope this helps someone else.

Add comment