Can I have an Express middleware that runs after the router but before the route handler?

Viewed 149

I want a generic middleware that can take the "req.route.path" value (e.g. "/foo/:id" and not the actual URL value "/foo/123") and return it on a HTTP response header called "X-ROUTE-NAME". The idea is for this route value to be recorded and logged by separate logging/metrics infrastructure and allow "group by" on the route name for other analysis purposes later.

Here's my first attempt:

app.use(function(req, res, next) {
  const {path} = req.route;
  console.log("The path was:", path);
  res.setHeader("X-ROUTE-NAME", req.route.path);
  next();
});

It doesn't work because if you put it BEFORE you register routes, it doesn't have any value in req.route.path yet. But if you register it AFTER the routes, you get an exception when you try to set the header if a handler has already sent content on the req.

So yah, the tricky part is figuring out how to snatch the "req.route.path" value AFTER it's set by the router (and saving it to the response as a header), but BEFORE a handler picks it up (since the handler can send content if it wants to and then you wont be able to set headers anymore).

Any ideas?

3 Answers

Use something like this :

router.use(routerModule) to route to the router module in main file.
And use router.get('/path', yourroute, actualCodeToBeExecuted) in the router module.

In this way, your yourroute be called after the router but before the actual functional route.

Hope this works!

app.use(function(req, res, next) {
  const {path} = req.route;
  console.log("The path was:", path);
  
  next();
  res.setHeader("X-ROUTE-NAME", req.route.path);
});

moving the setHeader should help

I found a solution that worked for me:

// create a test router and handler
const testRouter = express.Router();

testRouter.get('/test/:testId', (req, res) => {
  console.log("HANDLER");
  res.send("{}")
});

// create a function that injects a "beforeHandle" function into a router instance (depends on a private interface though)
function onBeforeHandle(router: IRouter, beforeHandle: (req: Request, res: Response) => void) {
  const methodName = "process_params";
  const routerAny: any = router;
  const oldFn = routerAny[methodName];
  
  if (!oldFn) {
    throw new Error(`No private '${methodName}' method found on router. This may be an incompatible version of Express.`);
  }
  
  if (oldFn.length !== 5) {
    throw new Error(`Invalid number of arguments on private '${methodName}' method. This may be an incompatible version of Express.`);
  }

  const newFn = (...args: any[]) => {
    const [, , req, res, handler] = args;

    beforeHandle(req, res);
    return oldFn.apply(routerAny, args as any);
  }
  routerAny[methodName] = newFn;
}

// function to be executed prior to the route handler
function setRouteNameHeader(req: Request, res: Response) {
  const routeHeaderName = "Q-ROUTE-NAME";
  if (!req.route) {
    console.log(`Can't add '${routeHeaderName}' header as 'req.route' is not set.`);
  } else {
    if (!res.headersSent) {
      res.setHeader(routeHeaderName, req.route.path);
    } else {
      console.log(`Can't add '${routeHeaderName} ${req.route.path}' header as headers were already sent.`);
    }
  }
}

// actually inject the function
onBeforeHandle(testRouter, setRouteNameHeader);
Related