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