How to add router middleware to dynamic Router?

430 views Asked by At

I am trying to add router middleware to my dynamically-generated routes. I want to pass an array of router middleware to my router so that it can attach them to my routes.

A route and/or a group of routes can have router middleware attached. Here are my types (RouterContext and RouterMiddleware come from Oak middleware framework):

export type RouteGroup = {
  group: { 
    prefix: string;
    middleware: Array<RouterMiddleware>;
  };
  routes: Array<Route>;
}

type Route = {
  method:
    | "all"
    | "delete"
    | "get"
    | "head"
    | "options"
    | "patch"
    | "post"
    | "put";
  path: string;
  middleware: Array<RouterMiddleware>;
  handler: (ctx: RouterContext) => Promise<void>;
}

I have one endpoint (healthcheck) that just returns a 200. I want to add a header to it with router middleware. Here is my router class:

export class Router {
  router: OakRouter;   // imported Oak's Router class as oakRouter
  routeGroups = [
    healthcheck,
  ]
  
  constructor() {
    this.router = new OakRouter({ prefix: "/api" })
  }

  registerRoutes() {
    this._generateRoutes(this.routeGroups)
  }

  private _generateRoutes(routeGroups: RouteGroup[]) {
    routeGroups.forEach(({ group, routes }) => {
      routes.forEach(({ method, path, middleware = [], handler }) => {
        (this.router[method] as OakRouter["all"])(
          group.prefix + path, 
          [...group.middleware ?? [], ...middleware], // <-- error line
          handler
        );
      })
    })
  }
}

I'm trying to initialize my group middleware and route middleware with empty arrays if there is no router middleware passed, otherwise spread the router middlewares that were passed. Anyway, here is the error message:

error: TS2769 [ERROR]: No overload matches this call.
  Overload 1 of 2, '(name: string, path: string, middleware: RouterMiddleware<RouteParams, Record<string, any>>, ...middlewares: RouterMiddleware<RouteParams, Record<string, any>>[]): Router<...>', gave the following error.
    Argument of type 'RouterMiddleware<RouteParams, Record<string, any>>[]' is not assignable to parameter of type 'string'.  

  Overload 2 of 2, '(path: string, middleware: RouterMiddleware<RouteParams, Record<string, any>>, ...middlewares: RouterMiddleware<RouteParams, Record<string, any>>[]): Router<...>', gave the following error.
    Argument of type 'RouterMiddleware<RouteParams, Record<string, any>>[]' is not assignable to parameter of type 'RouterMiddleware<RouteParams, Record<string, any>>'.
      Type 'RouterMiddleware<RouteParams, Record<string, any>>[]' provides no match for the signature '(context: RouterContext<RouteParams, Record<string, any>>, next: () => Promise<unknown>): unknown'.
          [...group.middleware ?? [], ...middleware], 

The error is probably telling me everything I need to know, I just don't know how to translate it into a workable solution. How do I alter my array of middleware to match the signature that the compiler is looking for?

1

There are 1 answers

2
Zwiers On BEST ANSWER

The error says:

Argument of type 'RouterMiddleware<RouteParams, Record<string, any>>[]' is not assignable to parameter of type 'RouterMiddleware<RouteParams, Record<string, any>>'

The router method can take a single middleware as the second argument, and an arbitrary number of middlewares after that (see the API docs). You can still use spread syntax for those, just without the []:

// Remove the first element in the array and return it
const firstMiddleware = middleware.shift();

(this.router[method] as OakRouter["all"])(
  group.prefix + path,
  firstMiddleware,
  ...group.middleware,
  ...middleware,
  handler,
);