Some tangential questions have been asked already. but this seems different - I am asking for a way to nicely instrument Express 5.0, not predict matched routes.
Related: How can I find the originally matched route at the time of a request in Express.Js
What I would like to do is instrument express in such a way that for every route handler that is matched, the matching string pattern is added to an array of handlers for the given request, so that a later handler can see the "path" a request took through the app. I really don't want to have to go modify every route in my app to instrument itself with req.route
.
For example, what is an implementation of onRouteCalled
where, given this code:
onRouteCalled(app, (req, route) => {
req.myMatchedRoutes ??= [];
req.myMatchedRoutes.push(route);
});
app.use(function logPath(req, res, next) {
res.on('close', () => {
// In the close event, req.route is available for the last route, but doesn't seem to include the information that would give me a full path. E.g. it only patches up to the last Router().
console.log(req.myMatchedRoutes);
});
next();
});
const router = Router();
router.use('/bacon/:tastiness', (req, res, next) => next());
router.use('/:food/:tastiness', (req, res, next) => {
res.sendStatus(204);
});
app.use(router);
A request like GET /bacon/delicious
would log ['/bacon/:tastiness', '/:food/:tastiness']
to the console?
Express doesn't store this kind of data because it doesn't need it. This would require to monkey-patch express.Route.prototype
methods to wrap route handler function with yours that does the extra job. This is the relevant implementation of express.Route
in Express 5 for reference.
Patching all request handlers would require more work, but for route handlers it would be something like:
const { METHODS } = require('node:http');
const httpMethods = METHODS.map((method) => method.toLowerCase());
const routeMethods = [...methods, 'all'];
const originalMethods = {};
for (const method of routeMethods) {
originalMethods[method] = express.Route.prototype[method];
express.Route.prototype[method] = function (...args) {
const { path } = this;
for (let i = 0; i < args.length; i++) {
const handler = args[i];
if (typeof handler === 'function' && handler.name !== 'patchedHandler') {
args[i] = function patchedHandler(req, res, next) {
req.myMatchedRoutes ??= [];
req.myMatchedRoutes.push(path); // also req.route.path
return handler.call(this, req, res, next);
}
}
}
return originalMethods[method].apply(this, args);
};
}
This currently doesn't support this kind of use, app.get('/example/c', [cb0, cb1, cb2])
.
Alternatively, this could be done by patching Route.prototype.dispatch
.