Replies: 6 comments 7 replies
|
If pillarjs/router#122 is agreed upon, then the following function will solve this case. * Recursively prints the registered routes of an Express router.
*
* @param {Array} routerStack - The stack of middleware and routes from an Express router.
* @param {string} [parentPath=''] - The parent path to prepend to the route paths.
*/
function printRegisteredRoutes(routerStack, parentPath = '') {
routerStack.forEach((middleware) => {
if (middleware.route) {
console.debug(
middleware.route.stack[0].method.toUpperCase(),
`${parentPath}${middleware.route.path}`
);
} else if (middleware.name === 'router') {
printRegisteredRoutes(
middleware.handle.stack,
`${parentPath}${middleware.path}`
);
}
});
} |
|
This is a hack around, I wouldn't promote running it in production but in a pipeline or locally it should solve the problem of targeting routers. It may require some shuffling of entry points but it works. // src/routes/user.ts
import type { Express, } from 'express';
export const userRouter = express.Router();
// Must be named function or a first class function
const someMiddleware: RequestHandler = (req, res, next) => {
next();
};
const someOtherMiddleware: RequestHandler = (req, res, next) => {
next();
};
userRouter.get('/', someMiddleware, function getUsers(req, res, next) { next() });
userRouter.post('/', someMiddleware, function getUsers(req, res, next) { next() });
userRouter.delete('/:id', function getUsers(req, res, next) { next() });
userRouter.get('/workspace/:id', someMiddleware, someOtherMiddleware, function getUserWorkspace(req, res, next) { next() });
userRouter.delete('/workspace/:id', function deleteUserWorkspace(req, res, next) { next() });
// Handler will be "<anonymous>"
userRouter.put('/workspace/:id', (req, res, next) => { next() });// src/routes/workspace.ts
import type { Express } from 'express';
export const workspaceRouter = express.Router();
// Must be named function or a first class function
const someMiddleware: RequestHandler = (req, res, next) => {
next();
};
workspaceRouter.get('/', someMiddleware, function getWorkspace(req, res, next) { next() });
workspaceRouter.post('/', someMiddleware, function createWorkspace(req, res, next) { next() });
workspaceRouter.delete('/:id', function deleteWorkspace(req, res, next) { next() });/// src/routes/index.ts
// Initialize the endpoints in a central location so it can be used by the server.ts or this generate.ts
import type { Express } from 'express';
import { userRouter } from './workspace.js';
import { workspaceRouter } from './workspace.js';
export function init(app: Express) {
app.use('/user', userRouter);
app.use('/workspace', workspaceRouter);
}// src/scripts/express-generate.ts
// Load env in case routes require then
import 'dotenv/config';
import 'dotenv-expand/config';
import express from 'express';
import type { Express, RequestHandler, IRouter } from 'express';
import { init } from '../routes/index.js';
const app: Express = express();
type ApplicationUse = typeof app.use;
type ILayer = IRouter['stack'][0];
// Bind the app.use so that the this context is available.
const originalUse: ApplicationUse = app.use.bind(app);
interface PathObject {
fullPath: string;
method: string;
middleware: string[];
handler: string;
}
function analyzeExpressPaths(router: IRouter, parentPath = '') {
for (const layer of router.stack) {
if (layer.name === 'handle' && layer.route) {
const fullPath = `${parentPath}${layer.route.path}`;
const path: PathObject = {
fullPath,
method: 'unknown',
middleware: [],
handler: 'unknown'
};
const numHandlers = layer.route.stack.length;
// Iterate over the RequestHandlers
for (let index = 0; index < numHandlers; index += 1) {
const middleware = layer.route.stack[index] as ILayer;
// The last argument will be the route handler
if (index >= numHandlers - 1) {
path.method = middleware.method.toUpperCase();
// This requires named variables/functions
path.handler = middleware.name;
} else {
// This requires named variables/functions
path.middleware.push(middleware.name);
}
}
console.log(path.method, path.fullPath, path.handler, path.middleware);
// Recursively call until all routers are covered.
} else if (layer.name === 'router' && layer.handle) {
analyzeExpressPaths(
layer.handle as IRouter,
parentPath
);
}
}
}
// Override the implementation of app.use
app.use = function (...args: Parameters<ApplicationUse>) {
// If this is a regular router then we're doing to try interpret the router object.
if (typeof args[0] === 'string') {
const basePath = args[0];
// Iterate over the remaining objects to find the router function
for (let i = 1; i < args.length; i += 1) {
const arg = args[i];
if (typeof arg === 'function' && arg.name === 'router' && basePath) {
// We now know the arg is a Router.
analyzeExpressPaths(arg, basePath);
}
}
}
originalUse(...args);
} as ApplicationUse;
// Initialize the app to load the routers
init(app);npx tsx ./src/scripts/express-generate.ts
GET /users/ getUsers [ 'someMiddleware' ]
POST /users/ getUsers [ 'someMiddleware' ]
DELETE /users/:id getUsers []
GET /users/workspace/:id getUserWorkspace [ 'someMiddleware', 'someOtherMiddleware' ]
DELETE /users/workspace/:id deleteUserWorkspace []
PUT /users/workspace/:id <anonymous> []
GET /workspace/ getWorkspace [ 'someMiddleware' ]
POST /workspace/ createWorkspace [ 'someMiddleware' ]
DELETE /workspace/:id deleteWorkspace []Then in your main app you can do something like this so that you're not modifying the app.use function in production. // src/app.ts
import { init } from '../routes/index.js';
const app: Express = express();
// Initialize the app to load the routers
init(app);
app.listen(() => {
}) |
|
en express v5 quitaron layer.regexp asi que hay que tirar de otra forma. puedes recorrer el stack del router a pelo: function getRoutes(app) {
const routes = []
app._router.stack.forEach((layer) => {
if (layer.route) {
const methods = Object.keys(layer.route.methods).map(m => m.toUpperCase())
routes.push({ path: layer.route.path, methods })
} else if (layer.name === "router" && layer.handle.stack) {
layer.handle.stack.forEach((sublayer) => {
if (sublayer.route) {
const methods = Object.keys(sublayer.route.methods).map(m => m.toUpperCase())
routes.push({ path: sublayer.route.path, methods })
}
})
}
})
return routes
}no es lo mas limpio del mundo pero funciona. si tienes subrouters muy anidados tendras que hacer la recursion un poco mas profunda pero la idea es esa |
|
I have an alternative hack to @wattry 's solution. Mine does not require any init() function to wrap the base routes. Like @wattry 's solution, you definitely should not run this in production, but for development or for documentation reasons it may be helpful
const express = require('express');
const { randomUUID } = require('crypto');
// ids are stored in the express routers
// it may be possible to use an incrementing variable, but I was worried about concurrency
function genId() {
return randomUUID();
}
// helper functions
function getRoot() {
return nodes.get(root);
}
function join(a, b) {
return (a + '/' + b).replace(/\/+/g, '/').replace(/\/$/, '').replace(/^\//, '') || '/';
}
function normalize(p) {
return p === '/' ? '/' : '/' + p;
}
const nodes = new Map();
function attach (parentId, childId, path) {
const parent = nodes.get(parentId);
const child = nodes.get(childId);
child.path = path;
parent.children.push(child)
}
function createNode(path = '') {
const node = {
id: genId(),
path,
children: [],
routes: [],
};
nodes.set(node.id, node);
return node.id;
}
const root = createNode();
// patch Router()
const originalRouter = express.Router;
express.Router = function (...args) {
const router = originalRouter.apply(this, args);
router.__node = createNode();
patchRouter(router);
return router;
};
function patchRouter(router) {
const originalUse = router.use;
router.use = function (...args) {
const [path, maybeRouter] = args;
if (typeof path === 'string' && maybeRouter?.stack) {
if (!this.__node) {
this.__node = createNode();
}
if (!maybeRouter.__node) {
maybeRouter.__node = createNode();
}
attach(this.__node, maybeRouter.__node, path);
}
return originalUse.apply(this, args);
};
const methods = ['get', 'post', 'put', 'delete', 'patch'];
for (const method of methods) {
const original = router[method];
router[method] = function (path, ...handlers) {
if (!this.__node) {
this.__node = createNode();
}
nodes.get(this.__node).routes.push({
method: method.toUpperCase(),
path,
});
return original.call(this, path, ...handlers);
};
}
}
// patch app.use
const originalAppUse = express.application.use;
express.application.use = function (...args) {
const [path, maybeRouter] = args;
if (typeof path === 'string' && maybeRouter?.stack) {
if (!this.__node) {
this.__node = root;
}
if (!maybeRouter.__node) {
maybeRouter.__node = createNode();
}
attach(this.__node, maybeRouter.__node, path);
}
return originalAppUse.apply(this, args);
};
// TODO: monkey-patch any other methods that I don't personally use in my app
// examples being `router.use(['/route1','/route2'], ...);` or `router.route()`
// ...
// api
function flattenRoutes () {
const result = [];
function walk(node, prefix = '') {
for (const r of node.routes) {
const full = join(prefix, r.path);
result.push({
method: r.method,
path: normalize(full)
});
}
for (const child of node.children) {
const nextPrefix = join(prefix, child.path);
walk(child, nextPrefix);
}
}
walk(getRoot());
return result;
}
function getAllPaths() {
const paths = flattenRoutes();
let output = {};
for (const path of paths) {
if (output[path.method] === undefined) output[path.method] = [];
output[path.method].push(path.path);
}
for (const method of Object.keys(output)) {
output[method] = output[method].sort();
}
return output;
}
express.__trace = {
getRoot,
flattenRoutes,
getAllPaths
}Usage is simple: require('./express-tracer.js'); // must be before requiring express
const express = require('express');
...
console.log(express.__trace.getAllPaths()); // gets paths organized by method
console.log(express.__trace.flattenRoutes()); // gets paths in object form
console.log(express.__trace.getRoot()); // gets the path tree
app.listen(...);Hope this helps someone! |
|
In Express 5 I would be careful about building this from private router internals. It can work for a quick debug script, but it tends to break across versions and nested routers. For production use, I prefer registering routes through a tiny wrapper that also records metadata: const routes = []
function route(method, path, handlers) {
routes.push({ method, path })
app[method](path, ...handlers)
}
route('get', '/users/:id', [getUser])For nested routers, make the base path explicit: function mount(basePath, router, routerRoutes) {
for (const r of routerRoutes) {
routes.push({ ...r, path: basePath + r.path })
}
app.use(basePath, router)
}That registry can then power:
If this is only for local debugging, reading |
|
In Express 5 the internal layer shape changed, so older A practical approach that still works is to walk function listRoutes(stack, prefix = "") {
const out = [];
for (const layer of stack || []) {
if (layer.route?.path) {
const methods = Object.keys(layer.route.methods)
.filter((m) => layer.route.methods[m])
.map((m) => m.toUpperCase());
const paths = [].concat(layer.route.path);
for (const p of paths) out.push({ methods, path: prefix + p });
} else if (layer.name === "router" && layer.handle?.stack) {
// mount path is not always exposed cleanly; many apps pass a known prefix
out.push(...listRoutes(layer.handle.stack, prefix));
}
}
return out;
}
// Express 4/5 app:
const stack = app.router?.stack || app._router?.stack;
console.log(listRoutes(stack));Caveats:
If you share a minimal app skeleton (flat vs nested routers), we can tune the walker for your mount paths. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
In continue of this discussion #5858
After upgrade to stable v5 i can't find
layer.regexpanymore. So, prevoius soluton doesn't work anymore.Does anyone have any ideas on how to build a complete tree of subrouts? Thank you!
All reactions