I am asking someone to implement the feature.
I request a feature to print all available routes registered in very easy way to handle. Something like:
registeredRoutes()
And it would return for example an array of routes:
[{method: 'POST', path: '/api/v1/route1/:param1'},...]
I've seen many people online build their own solutions to retrieve all available routes, but every version of express changes a bit its structure and it gets harder to do so without very nasty looking tricks. I did mine too, but now I just don't know how to do it.
My current solution allows me to just copy the route and use it in postman, also helps with visibility, as I can just print all routes and see if I can find useful endpoint.
2026-05-19T09:29:45.793Z APP INFO, [App] Registered routes: 5
2026-05-19T09:29:45.794Z APP INFO, [Available Route] GET /api-docs
2026-05-19T09:29:45.794Z APP INFO, [Available Route] GET /api-docs.json
2026-05-19T09:29:45.794Z APP INFO, [Available Route] POST /api/data-integration/service-profile
2026-05-19T09:29:45.794Z APP INFO, [Available Route] GET /health
2026-05-19T09:29:45.794Z APP INFO, [Available Route] GET /sandbox/1
Right now I have a function called: showAppRoutes(app);
Its code looks like this:
import type { Express } from 'express';
import { logger } from '../infra/logger';
import { appRoutes, apiRoutes } from '../routes';
type RouteInfo = { method: string; path: string };
export const showAppRoutes = (app: Express): void => {
const routes: RouteInfo[] = [];
let stack: any[] = [];
const appAny = app as any;
if (!appAny._router && appAny.router) {
appAny._router = appAny.router.router;
}
stack = appAny?._router?.stack || [];
if (stack.length === 0 && appAny.router && appAny.router.stack) {
stack = appAny.router.stack;
}
// Create a map of router handles to their mount paths
const routerMountPaths: Map<any, string> = new Map([
[appRoutes, '/'],
[apiRoutes, '/api'],
]);
const extractRoutes = (layers: any[], prefix = '') => {
for (const layer of layers) {
if (layer.route) {
let path = layer.route.path || '';
// Build the full path, avoiding double slashes
if (prefix === '/' || prefix === '') {
path = (prefix === '/' ? '' : prefix) + path;
} else {
path = prefix + path;
}
const methods = Object.keys(layer.route.methods || {});
methods.forEach(method => {
routes.push({
method: method.toUpperCase(),
path: path || '/',
});
});
} else if (layer.name === 'router' && layer.handle?.stack) {
let routePrefix = '';
// Check if we know the mount path for this router
if (routerMountPaths.has(layer.handle)) {
routePrefix = routerMountPaths.get(layer.handle) || '';
}
// Otherwise check for __ROUTE_PREFIX__ metadata
else if ((layer.handle as any).__ROUTE_PREFIX__) {
routePrefix = (layer.handle as any).__ROUTE_PREFIX__;
}
extractRoutes(layer.handle.stack, prefix + routePrefix);
}
}
};
extractRoutes(stack);
const seen = new Set<string>();
const unique = routes
.filter(r => {
const key = `${r.method} ${r.path}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
})
.sort((a, b) => {
const pathCompare = a.path.localeCompare(b.path);
return pathCompare !== 0 ? pathCompare : a.method.localeCompare(b.method);
});
// Add Swagger UI endpoints (middleware-based)
const swaggerRoutes = [
{ method: 'GET', path: '/api-docs' },
{ method: 'GET', path: '/api-docs.json' },
];
const allRoutes = [...unique, ...swaggerRoutes]
.filter((v, i, a) => a.findIndex(t => t.method === v.method && t.path === v.path) === i)
.sort((a, b) => {
const pathCompare = a.path.localeCompare(b.path);
return pathCompare !== 0 ? pathCompare : a.method.localeCompare(b.method);
});
logger.info(`[App] Registered routes: ${allRoutes.length}`);
const METHOD_WIDTH = 8;
allRoutes.forEach(r => {
const methodPadded = r.method.padStart(METHOD_WIDTH, ' ');
logger.info(`[Available Route] ${methodPadded} ${r.path}`);
});
};
But I also need to put some variables across my app for it to work:
(router as any).__ROUTE_PREFIX__ = '/data-integration';
My previous code returned exactly the same format, but was cleaner.
import type { Express } from 'express';
import { logger } from '../infra/logger';
type RouteInfo = { method: string; path: string };
// Best-effort extraction of a mounted prefix from a router layer
function getLayerPrefix(layer: any): string {
// Prefer explicit path if present (Express may set it on some versions)
if (typeof layer.path === 'string') return layer.path;
// Fast root router
if (layer?.regexp?.fast_slash) return '';
const keys = Array.isArray(layer?.keys) ? layer.keys : [];
// Try to reconstruct from keys (e.g., /:id)
if (keys.length) {
return '/' + keys.map((k: any) => `:${k.name}`).join('/');
}
// Fallback: parse from regexp
const src = String(layer?.regexp || '');
// Typical: /^\/api\/v1\/users\/?(?=\/|$)/i
const match = src.match(/^\/\^\\\/(.*)\\\/\?\(\?\=\\\/\|\$\)\/i$/);
if (match && match[1]) {
return '/' + match[1].replace(/\\\//g, '/');
}
return '';
}
export const showAppRoutes = (app: Express): void => {
const stack: any[] = (app as any)?._router?.stack || [];
const routes: RouteInfo[] = [];
const walk = (layers: any[], prefix = '') => {
for (const layer of layers) {
if (layer?.route?.path) {
const fullPath = prefix + layer.route.path;
const methods = Object.keys(layer.route.methods || {});
for (const m of methods) {
routes.push({ method: m.toUpperCase(), path: fullPath });
}
} else if (layer?.name === 'router' && layer?.handle?.stack) {
const newPrefix = prefix + getLayerPrefix(layer);
walk(layer.handle.stack, newPrefix);
}
}
};
walk(stack);
// Deduplicate and sort alphabetically by path, then by method
const seen = new Set<string>();
const unique = routes.filter(r => {
const k = `${r.method} ${r.path}`;
if (seen.has(k)) return false;
seen.add(k);
return true;
}).sort((a, b) => {
// Primary sort: alphabetical by path
const pathCompare = a.path.localeCompare(b.path);
if (pathCompare !== 0) return pathCompare;
// Secondary sort: by method if paths are the same
return a.method.localeCompare(b.method);
});
logger.info(`[App] Registered routes: ${unique.length}`);
// Display with right-aligned method padding so last letters align
const METHOD_WIDTH = 8; // Width for HTTP method area (accommodates DELETE = 6 chars)
unique.forEach(r => {
// Right-align the method within the fixed width
const methodPadded = r.method.padStart(METHOD_WIDTH, ' ');
logger.info(`[Available Route] ${methodPadded} ${r.path}`);
});
};
I'd really uppreciate if someone had a look and the will to implement the feature.
I am asking someone to implement the feature.
I request a feature to print all available routes registered in very easy way to handle. Something like:
registeredRoutes()
And it would return for example an array of routes:
[{method: 'POST', path: '/api/v1/route1/:param1'},...]I've seen many people online build their own solutions to retrieve all available routes, but every version of express changes a bit its structure and it gets harder to do so without very nasty looking tricks. I did mine too, but now I just don't know how to do it.
My current solution allows me to just copy the route and use it in postman, also helps with visibility, as I can just print all routes and see if I can find useful endpoint.
2026-05-19T09:29:45.793Z APP INFO, [App] Registered routes: 5
2026-05-19T09:29:45.794Z APP INFO, [Available Route] GET /api-docs
2026-05-19T09:29:45.794Z APP INFO, [Available Route] GET /api-docs.json
2026-05-19T09:29:45.794Z APP INFO, [Available Route] POST /api/data-integration/service-profile
2026-05-19T09:29:45.794Z APP INFO, [Available Route] GET /health
2026-05-19T09:29:45.794Z APP INFO, [Available Route] GET /sandbox/1
Right now I have a function called: showAppRoutes(app);
Its code looks like this:
But I also need to put some variables across my app for it to work:
(router as any).__ROUTE_PREFIX__ = '/data-integration';My previous code returned exactly the same format, but was cleaner.
I'd really uppreciate if someone had a look and the will to implement the feature.