10
votes

I've been having a real difficult time trying to define custom middleware for our app. I'm using [email protected] and [email protected].

All the examples I've been seeing for typing middleware implies that the user is not adding anything to the req or res argument, but I know that that was kind of the point of middleware - mutate either one of those objects knowing that the middlewares had to be loaded in a certain order (ie. cookie-parser or body-parser was notorious for this in the early days, i guess still is?). Anyway, most of the examples net out to something like this github issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/26146#issuecomment-393386416

The problem I have with that is that we do mutate the req and res objects being passed along to each middleware so I've had to type both objects accordingly:

import {
    Express as ExpressInterface,
    Request as ExpressRequest,
    Response as ExpressResponse,
    NextFunction as ExpressNextFunction,
} from 'express';
import LoggerType from 'some-log-package';

// don't do anything different the `next()`
export type ExpressNextFunction = ExpressNextFunction;

export interface CustomServerApp extends ExpressInterface {
    customHeader: string;
    customLogger: LoggerType;
}

export interface CustomServerRequest extends ExpressRequest {
    app: CustomServerApp,
    id: string,
    urlSearchParams: URLSearchParams,
    // etc
}

export interface CustomServerResponse extends ExpressResponse { ... }

So I export the basic arguments for middleware:

  • req = CustomServerRequest
  • res = CustomServerResponse
  • next = ExpressNextFunction

Of course, I import those accordingly to my middleware files:

import {
    CustomServerRequest,
    CustomServerResponse,
    ExpressNextFunction
} from './server-types'

export const customMiddleware = (req: CustomServerRequest, res: CustomServerResponse, next: ExpressNextFunction): void {
    // do something here with either `req` or `res` or both!
    next()
};

We have a centralized middleware.ts file where we put all of our global middleware into so it looks something like this:

import { CustomServerApp } from './server-types';
import { customMiddleware } from './middlewares/customMiddleware';
export const middlewares = (app: CustomServerApp): void => {
    app.use(
        customMiddleware,
        // maybe more middlewares, etc
    );
}

However, then the type checker runs, I get errors like the following:

No overload matches this call.
  The last overload gave the following error.
    Argument of type '(req: Request, res: Response, next: NextFunction) => void' is not assignable to parameter of type 'PathParams'.
      Type '(req: Request, res: Response, next: NextFunction) => void' is missing the following properties from type '(string | RegExp)[]': pop, push, concat, join, and 27 more.ts(2769)

I've been having a real tough time understanding why this is getting caught on the last overload definition. Just based on the @types/express, it seems hard to add an additional overload to middleware handler even with type declaration merging.

Even when I follow a similar pattern to the core express types to define a middleware handler - https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express-serve-static-core/index.d.ts#L40-L43 - it doesn't work out either. It also doesn't seem like it's easy to extend from either - https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express/index.d.ts#L99 - since you can't pass a generic to the core RequestHandler, unlike the router definitions (IRouterHandler, IRouterMatcher). When I try and type it this way, I get different, but similar type errors.

Has anyone else experienced doing something similar?

1
You may want to look at this answer.Justice Bringer

1 Answers

3
votes

You can't add a required field in your req and res variable, because express can't satisfy the constraint for you. Also, express doesn't handle changing req/res type after passing through different middleware. So, your custom type can contain required fields the same that in ExpressRequest/ExpressResponse, the rest fields should be optional and should have nullcheck inside the middleware.