Search code examples
typescriptexpresscovariancecontravariance

how do I make the TypeScript compiler happy about variance when calling generic handlers (like expressJS middleware)


disclaimer: I am still a little fuzzy on variance in general...

My situation is as follows:

// index.ts
import express from 'express';
import {Request, Response} from 'express';
const app = express();
app.use(handler);

interface BetterRequest extends Request {
    foo: string;
}

function handler(req: BetterRequest, res: Response) {
    req.foo = 'bar';
}

// tsconfig.json
{
    "compilerOptions": {
        "target": "es6",
        "module": "commonjs",
        "strict": true,
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true
    }
}

The error that I get is

[ts]
Argument of type '(req: BetterRequest, res: Response) => void' is not assignable to parameter of type 'RequestHandlerParams'.
Type '(req: BetterRequest, res: Response) => void' is not assignable to type 'RequestHandler'.
    Types of parameters 'req' and 'req' are incompatible.
    Type 'Request' is not assignable to type 'BetterRequest'.
        Property 'foo' is missing in type 'Request'.

enter image description here

I understand what the error is saying and that I could either turn off those warnings from the tsconfig.json file or by a line comment. But that's not what I am after.

How do I properly write code to handle this situation?

is the following the only way?

// index.ts
function handler(req: Request, res: Response) {
    const req2 = req as BetterRequest;
    req2.foo = 'bar';
}

Solution

  • An explicit cast as you suggested is probably the best way if you know you are going to immediately set the foo property and you want to be able to use the property from that point on without checking whether it is defined. Another approach is to declare the foo property as optional; then a Request can be implicitly converted to a BetterRequest (so your handler can be implicitly converted to an Express handler), but the type of the foo property will include undefined and you'll have to deal with that every time you use the property.