List All Routes in express app #5858
Replies: 31 comments 4 replies
|
It is not possible to get a fill list with the built-in router. Since paths are regular expression-based, there is no easy way in which to reverse them into a path. |
|
Hmmm, I'm auditing a public facing application. Tons of repos and submodules, it would be almost laughable to do it manually. Is my only option then to catch them downstream via: |
|
**Creative and/or potentially crazy solutions are welcome :) |
|
There is no general solution that would guarantee you are not missing anything. Is it possible to share the app at all? We could at least see what kind of structure the program is using to see if it's possible at all. What you posted at first is a pretty simplistic method, and if that's not giving what you're looking for, it's hard to really understand why without seeing something. |
|
O I think I can share my sandbox app! (Way less code anyway.) Two main datapoints I'm trying to capture:
Sandbox: Output: |
|
Here is a very, very hacky way for that app. I can't stress how hacky this is, but here it is: function print (path, layer) {
if (layer.route) {
layer.route.stack.forEach(print.bind(null, path.concat(split(layer.route.path))))
} else if (layer.name === 'router' && layer.handle.stack) {
layer.handle.stack.forEach(print.bind(null, path.concat(split(layer.regexp))))
} else if (layer.method) {
console.log('%s /%s',
layer.method.toUpperCase(),
path.concat(split(layer.regexp)).filter(Boolean).join('/'))
}
}
function split (thing) {
if (typeof thing === 'string') {
return thing.split('/')
} else if (thing.fast_slash) {
return ''
} else {
var match = thing.toString()
.replace('\\/?', '')
.replace('(?=\\/|$)', '$')
.match(/^\/\^((?:\\[.*+?^${}()|[\]\\\/]|[^.*+?^${}()|[\]\\\/])*)\$\//)
return match
? match[1].replace(/\\(.)/g, '$1').split('/')
: '<complex:' + thing.toString() + '>'
}
}
app._router.stack.forEach(print.bind(null, []))That produces: |
|
Hey, I'll take hacky over nothing any day . :) I'll have to try this against our other apps and get back to you! Thanks for looking into this so quickly!! |
|
It's been working perfectly for our simpler projects, pretty confident it'll work for our more convoluted apps as well, but I will keep you posted. :) I wanted to check in with you before I did it, but there's this stackoverflow post of others who have tried to get this working, would it be ok if I posted your hacky albeit effective solution? |
|
It's the only I found that realy works so far ... good job |
|
Thanks, @dougwilson! This is the only solution working. I hope the next version do not break this code :D |
|
Same topic discussed and addressed on StackOveflow. |
|
I've found this package, which seems to work properly: https://github.com/AlbertoFdzM/express-list-endpoints ! |
|
I wrote a package to list middleware and routes mounted on an app quite a while ago: https://github.com/ErisDS/middleware-stack-printer Maybe its useful for someone else. |
|
Why don't you try something like swagger using tsoa(it automatically generates a swagger.json file) which will have all the api liat including what parameters it takes. Just one note, tsoa requires Typescript. |
|
@surendra-y - the problem I've had with swagger & friends (and tsoa looks similar) is that they all depend on some sort of non-authoritative, duplicate source of route information: annotations, jsdoc, etc. Which only works long term if you're disciplined enough where you probably don't need it in the first place. The great thing about doing it via reflection is:
That said, if whatever middleware does this also spits out the result in OpenAPI format - even better! |
|
Seems like that there are also some parasites on the internet, who steal your idea and sell them as their own... https://medium.com/@stupid_arnob/get-all-api-path-with-method-in-a-single-api-request-f6116254ea1a |
|
Hi if someone is still searching, I rewrote the solution of @dougwilson in Typescript and made it output an array of strings of your routes so you can do with them what you wish. And then you can call it like this: |
|
We are always listening 😀 |
|
Actually I wrote it as an middleware and in a singleton pattern. This is useful if you dont add routes on runtime, every route has only one Routehandler. So you can save the routes into a Map one time and can have a fast lookup. I used it to match the routes to the RouteHandlers and now I can lookup what route a routehandler has. |
|
I wrote up a dependency free Typescript npm package that parses complex express apps and outputs a list of data per route and allows attaching meta-data to each route, if desired. |
|
@nklisch I appreciate your library very much. However, I am afraid it does not dig up to the middleware level. Take, for example, my app available here: I use three libraries, Could you please take a look at this abnormal behaviour? |
|
@brunolnetto But for swagger-stats, I looked into how they do their routing, and unfortunately they have a custom solution that doesn't use Expresses built-in route matching, seen here: This means it is impossible for someone to scan the Express stack to figure out these routes, as they are hidden inside custom logic, inside a middleware. |
|
Hey y'all – to my understanding NestJS uses express for routing – how can I grab the
|
|
Is there anyone that renders it as an HTML file? |
|
Hi @wesleytodd and @blakeembrey! |
|
made a file that offers a more user-friendly workaround, if you're working with a
const fs = require("fs");
const path = require("path");
const ROUTES_DIR = path.join(__dirname, "/backend/routes");
const OUTPUT_FILE = path.join(__dirname, "routes-summary.txt");
const methods = ["get", "post", "put", "delete", "patch"];
const routeRegex = new RegExp(`router\\.(${methods.join("|")})\\(['"\`]([^'"\\\`]+)['"\`]`, "i");
const files = fs.readdirSync(ROUTES_DIR).filter(f => f.endsWith(".route.js"));
let result = [];
for (const file of files) {
const prefix = "/" + file.replace(".route.js", "");
const lines = fs.readFileSync(path.join(ROUTES_DIR, file), "utf-8").split("\n");
const routes = lines
.map(line => line.trim())
.map(line => {
const match = routeRegex.exec(line);
if (!match) return null;
const method = match[1].toUpperCase();
const subPath = match[2].startsWith("/") ? match[2] : "/" + match[2];
return `${method} ${prefix}${subPath}`;
})
.filter(Boolean);
if (routes.length) {
result.push(`${file}`);
result.push(...routes);
result.push(""); // Empty line for separation
}
}
fs.writeFileSync(OUTPUT_FILE, result.join("\n"), "utf-8");
console.log(`Extracted ${result.length} lines to ${OUTPUT_FILE}`); |

Here is a very, very hacky way for that app. I can't stress how hacky this is, but here it is: