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.
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",
]
...
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
}
}
}
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.
Happy Coding, everyone!
Latest comments (44)
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 usetsc watch
i got the following errorerror TS2339: Property 'currentUser' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>.
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 innode_modules
🙂you're great)
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) => {
...
})
The best article about that I've found today in the Internet.
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
Thanks!
Thanks! Same problem here.
NOTE that you have to add
typeRoots
insidecompilerOptions
in thetsconfig.json
file.What if we directly extends the Request interface like:
export interface CustomReq extends Request{
currentUser: User
}
and use this instead?
This works as well 🙂
thanks ...this helped alot
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 🤷♂️.
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 ?
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.
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.
Thanks Bro
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.
I'm really glad you found it useful.
This was awesome! I was missing the step with having to create the same folder path structure and adding the "typeRoots"