extending express request class in Flow
Asked Answered
S

2

6

I am building a nodeJS app using Flow, and I need to extend the default express annotation for express$Request to accommodate other fields that I tack on, like .user and .session.

unfortunately, when I try to do this and create middleware that accepts this new Request type, Flow freaks out and I'm not sure what I'm doing wrong.

the original code for express from flow-typed is:

declare class express$Request extends http$IncomingMessage mixins express$RequestResponseBase {
    ....
}

declare type express$Middleware = 
    ((req: express$Request, res: express$Response, next: express$NextFunction) => mixed) |
    ((error: ?Error, req: express$Request, res: express$Response, next: express$NextFunction) => mixed);

so I thought I would just extend express$Request and then all of my middleware should work with the new properties, right?

declare class web$Request extends express$Request {
    user: any,
    isAuthenticated(): boolean,
    session: {
      loginForwardUrl: ?string,
    },
}

const authenticationMiddleware: express$Middleware = (
  req: web$Request, res, next
): mixed => {
  if (req.isAuthenticated()) {
    return next();
  }

  req.session.loginForwardUrl = req.originalUrl;
  return res.redirect('/auth/login/google');
}

unfortunately, this yields the super-complex error:

function
This type is incompatible with
union: function type(s): web/src/index.js:113
Member 1:
function type: flow-typed/npm/express_v4.x.x.js:97
Error:
web$Request: web/src/index.js:114
This type is incompatible with the expected param type of
express$Request: flow-typed/npm/express_v4.x.x.js:97
Member 2:
function type: flow-typed/npm/express_v4.x.x.js:98
Error:
web$Request: web/src/index.js:114
This type is incompatible with an argument type of
null: flow-typed/npm/express_v4.x.x.js:98

can anyone explain what's going on here and how to fix it?

thanks!

Shel answered 20/6, 2017 at 23:30 Comment(0)
S
5

The error says that an argument/param of type express$Request (Member 1) or null (Member 2) was expected, but web$Request was seen.

Unfortunately, Flow does not support extending/overriding flow/lib types:

https://github.com/facebook/flow/issues/396

What I've begun to do is:

  1. flow-typed install [email protected]
  2. Move express_v4.x.x.js from flow-typed/npm/ to flow-typed/ (outside flow-typed/npm/ so it won't be overwritten by future flow-typed installs, and inside flow-typed/ so flow will automatically make declare blah statements global)
  3. Right below the declare class express$Request... (so it's easy to find and so it's above where it's used inside declare module..., I put:

    declare class express$Request extends express$Request { user: any; isAuthenticated(): boolean; session: { loginForwardUrl: ?string; }; }

I do this instead of putting my custom props on the original class so that it's easy to see which props are custom.

Selflove answered 28/9, 2017 at 21:53 Comment(6)
oof, brutal -- wish that Flow would support this better. thank you!Shel
Where do you do the declare class?Gassy
@Shel there might be a way: see twitter.com/yawaramin/status/1171954935082168321Sam
@Sam I agree that is a better way, if I understand it correctly (it's been years since I've worked with Flow or express). Given the file in the gist, you'd just declare the type of your router as TypedRouter then everything is fine?Selflove
@Shamoon, please see steps 2 and 3. But, Yawar's way might be better.Selflove
Yeah, if you use TypedRouter instead of express$Router, and use the appropriate typed versions of the routing methods, and declare all the middleware with the correct types, then it works :-) e.g. middleware: function addSessionId(req: $Request & {-id: string}): void { req.id = 'random-id' }. Note the - which makes id a contravariant field of the extended object type, i.e. it's write-only.Sam
L
0

If you prefer to not modify the original flow-typed/npm/express_v4.x.x.js you can use flow intersection types:

import type {$Request} from 'express';

type MyType = {
  foo: string
}:

export type CustomRequest = $Request & {
  foo: MyType | void;
  bar: string | void
};

I like this approach because I can add my own type definitions to CustomRequest. Which can be tricky when extending $Request inside typed/npm/express_v4.x.x.js file.

Lacedaemon answered 1/12, 2020 at 10:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.