Home > Back-end >  Express use custom typed middleware
Express use custom typed middleware

Time:11-09

I am trying to write authentication logic for my app. I wrote a middleware that verifies a token, and therefore I need req.id. I had to write an interface that extends from express.Request to be able to access id (similar issue).

import { Request } from "express";

export interface IGetUserAuthInfoRequest extends Request {
    id: number;
}

This is how I implement the interface in my middleware to be able to use the req.id property.

export const verifyToken = (req: IGetUserAuthInfoRequest, res: Response, next: NextFunction) => {
    req.id = 1; // do something with req.id
    /**
     * some more logic
     **/ 
    next(); 
}

When I try calling the middleware, TypeScript complains, I think this is because of the custom type.

app.get("/api/try/admin", verifyToken, (req: Request, res: Response) => {
    res.json({message: "try admin"})
});

The error looks something like this.

Type '(req: IGetUserAuthInfoRequest, res: Response, next: NextFunction) 
=> Response | void' is not assignable to type 
'ErrorRequestHandler<ParamsDictionary, any, any, ParsedQs, Record<string, any>> | 
RequestHandler<ParamsDictionary, any, any, ParsedQs, Record<...>>'.

How could I type this correctly (preferably without using any)?

CodePudding user response:

Your custom middleware is not compatible with (not a subtype of) the base Express middleware type.

Your custom middleware's req parameter is more specialized than the base middleware's Request parameter. In type theory language, your custom middleware is NOT contravariant with the base Express middleware in their argument types. Function argument contravariance is a requirement for substitution/subtyping.

One solution to this is to turn off strictFunctionChecks in tsconfig.json to allow function parameter bivariance. Another way is to change your custom request parameter's type to make it compatible with the base type:

interface IGetUserAuthInfoRequest extends Request {
  id?: number // making this optional will make your custom middleware type-compatible
}

With these workarounds, your custom middleware will be accepted by app.get, BUT its customized req parameter will NOT be propagated to succeeding middlewares and request/error handlers. That is, req.id will still be type undefined instead of number | undefined in other places (it will only be typed inside your custom middleware).

I think the proper solution to this is to use declaration-merging and merge your custom Request interface to the Express namespace. Express exposes this feature for custom middlewares to use:

declare global {
    namespace Express {
        // These open interfaces may be extended in an application-specific manner via declaration merging.
        // See for example method-override.d.ts (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/method-override/index.d.ts)
        interface Request {}
        interface Response {}
        interface Application {}
    }
}

To merge your custom middleware's Request interface:

declare global {
  namespace Express {
    interface Request {
      id?: number
    }
  }
}
  • Related