I’m trying to use middleware in Express for my node backend, and facing some Typescript issues regarding modifying / attaching fields to the req object.
My goal is to be able to write route handlers that assume we have the given context, to not repeat code. For instance, I can write a route like this:
const myRouteHandler = async (req: ExpressRequestWithUserContext, res: express.Response) {
// I can easily access my user context, req.user, without typescript warnings
}
This feels natural, because I don’t want to first check for req.user
on every route that requires a user context, which is repeating a lot of code.
Of course we still need to make sure handle the case where we don’t have a user context, at some point. I do this by wrapping all my route handlers in a wrapper function, authWrapper
, which checks if we have a valid user context, and if not, sends an error.
function authWrapper(controller: asnyc (req: ExpressRequestWithUserContext, res: express.Response) => Promise<void>) {
return async (req: Express.Request, res: Express.Response) => {
const idToken = req.headers(...); // Parse the JWT token sent in the request
const decodedUser = await myValidatorFunction(idToken);
if (!decodedUser) {
res.status(400).send('Invalid authorization');
}
(req as ExpressRequestWithUserContext).user = decodedUser;
return controller((req as ExpressRequestWithUserContext), res);
}
}
Now, when I register my routes to express, I can register them like this:
app.get(myPath, authWrapper(myRouteHandler))
And the types stay compatible with what express expects.
It also means I don’t need to duplicate the auth-verification code for every route.
However, I also want to access req.file
for some routes, when the user uploads an image. I use a middleware called multer, which extends the definition of an express Request to include an optional .file
property. But again, I don’t want to first check if req.file
exists in all my routes that need access to a file. How can I write code that allows me to do this? I can do something similar to my authWrapper code, but the issue is if I need routes that require both a user context, and a file, it becomes hard to compose. I don’t want to create a type ExpressRequestWithUserContextAndFile
, because as I start to build up middlewares, this gets messy and hardcoded.
Is there a way to create composable wrappers for my controllers that attach the necessary information to the req
and maintain typings?