Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package-lock=false
min-release-age=7
ignore-scripts=true
allow-git=none
allow-git=root
27 changes: 26 additions & 1 deletion lib/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,24 @@ app.use = function use(fn) {
fn.parent = this;

// restore .app property on req and res
router.use(path, function mounted_app(req, res, next) {
function mounted_app(req, res, next) {
var orig = req.app;
fn.handle(req, res, function (err) {
Object.setPrototypeOf(req, orig.request)
Object.setPrototypeOf(res, orig.response)
next(err);
});
}

// Expose the sub-app router without initializing it before mount.
Object.defineProperty(mounted_app, 'router', {
get: function getrouter() {
return fn.router;
}
});

router.use(path, mounted_app);

// mounted an app
fn.emit('mount', this);
}, this);
Expand All @@ -257,6 +266,22 @@ app.route = function route(path) {
return this.router.route(path);
};

/**
* List the routes and mounted routers registered on this application.
* See Router#listRoutes() documentation for the returned entries.
*
* Mounted sub-applications expose their underlying router in each entry.
* Nested routes are not expanded; call `entry.router.listRoutes()` to
* inspect them.
*
* @return {Array}
* @public
*/

app.listRoutes = function listRoutes() {
return this.router.listRoutes();
};

/**
* Register the given template engine callback `fn`
* as `ext`.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"proxy-addr": "^2.0.8",
"qs": "^6.16.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"router": "git+https://github.com/bjohansebas/router.git#e59071e415da5983ab901f5d81597b424fda4bdf",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: This commit isn't in your PR yet, I assume it will?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was, but I rebased, so the commit hashes changed. To test it locally, you’ll need to use the latest commit hash from that PR.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the wrapper change in listRoutes, there's no handle.router check. I think it had this?

if (!route) {
  const handle = layer.handle
  const candidate = Array.isArray(handle.stack) ? handle : handle.router  // L469

  if (candidate && Array.isArray(candidate.stack)) {
    router = candidate
  }
}

Can you double check if it is there?

"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
Expand Down
214 changes: 214 additions & 0 deletions test/app.listRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
'use strict';

var assert = require('node:assert');
var methods = require('node:http').METHODS;
var express = require('../');
var request = require('supertest');

describe('app.listRoutes()', function () {
it('should return an empty array for a new application', function () {
var app = express();

assert.deepStrictEqual(app.listRoutes(), []);
});

it('should list routes registered through the application APIs', function () {
var app = express();

app.get('/users', noop);
app.route('/posts').get(noop).all(noop).post(noop);
app.route('/draft');

assert.deepStrictEqual(app.listRoutes(), [
{ path: '/users', methods: ['GET', 'HEAD'], router: undefined },
{ path: '/posts', methods: ['GET', '*', 'POST', 'HEAD'], router: undefined },
{ path: '/draft', methods: [], router: undefined }
]);
});

it('should list the individual HTTP methods registered by app.all()', function () {
var app = express();

app.all('/all', noop);
app.route('/wildcard').all(noop);

assert.deepStrictEqual(app.listRoutes(), [
{ path: '/all', methods: methods, router: undefined },
{ path: '/wildcard', methods: ['*'], router: undefined }
]);
});

it('should expose mounted routers for separate inspection', function () {
var app = express();
var router = express.Router();

router.all('/users', noop);
app.use('/api', router);

var routes = app.listRoutes();

assert.deepStrictEqual(routes, [
{ path: '/api', methods: undefined, router: router }
]);
assert.deepStrictEqual(routes[0].router.listRoutes(), [
{ path: '/users', methods: ['*'], router: undefined }
]);
});

it('should omit plain middleware', function () {
var app = express();

app.use(noop);
app.use('/admin', noop);
app.get('/users', noop);

assert.deepStrictEqual(app.listRoutes(), [
{ path: '/users', methods: ['GET', 'HEAD'], router: undefined }
]);
});

it('should expose the router of a mounted sub-application', function () {
var app = express();
var admin = express();

admin.get('/users', noop);
app.use('/admin', admin);

var routes = app.listRoutes();

assert.deepStrictEqual(routes, [
{ path: '/admin', methods: undefined, router: admin.router }
]);
assert.deepStrictEqual(app.router.listRoutes(), routes);
assert.deepStrictEqual(routes[0].router.listRoutes(), [
{ path: '/users', methods: ['GET', 'HEAD'], router: undefined }
]);
});

it('should preserve registration order and the paths of sub-application mounts', function () {
var app = express();
var admin = express();
var path = /^\/admin/;

app.get('/before', noop);
app.use(admin);
app.use(['/one', ['/two']], admin);
app.use(path, admin);
app.get('/after', noop);

assert.deepStrictEqual(app.listRoutes(), [
{ path: '/before', methods: ['GET', 'HEAD'], router: undefined },
{ path: '/', methods: undefined, router: admin.router },
{ path: '/one', methods: undefined, router: admin.router },
{ path: '/two', methods: undefined, router: admin.router },
{ path: path, methods: undefined, router: admin.router },
{ path: '/after', methods: ['GET', 'HEAD'], router: undefined }
]);
});

it('should allow inspection of nested sub-applications through their routers', function () {
var app = express();
var api = express();
var admin = express();

app.use('/api', api);
api.use('/admin', admin);
admin.get('/users', noop);

var apiRouter = app.listRoutes()[0].router;
assert.strictEqual(apiRouter, api.router);
assert.deepStrictEqual(apiRouter.listRoutes(), [
{ path: '/admin', methods: undefined, router: admin.router }
]);
assert.deepStrictEqual(apiRouter.listRoutes()[0].router.listRoutes(), [
{ path: '/users', methods: ['GET', 'HEAD'], router: undefined }
]);
});

it('should expose sub-applications mounted directly on an express.Router()', function () {
var app = express();
var router = express.Router();
var admin = express();

app.use('/api', router);
router.use('/admin', admin);
admin.get('/users', noop);

assert.deepStrictEqual(app.listRoutes()[0].router.listRoutes(), [
{ path: '/admin', methods: undefined, router: admin.router }
]);
});

it('should reflect routes added after inspecting an empty mounted sub-application', function () {
var app = express();
var admin = express();

app.use('/admin', admin);

var mountedRouter = app.listRoutes()[0].router;
assert.strictEqual(mountedRouter, admin.router);
assert.deepStrictEqual(mountedRouter.listRoutes(), []);

admin.get('/users', noop);

assert.deepStrictEqual(mountedRouter.listRoutes(), [
{ path: '/users', methods: ['GET', 'HEAD'], router: undefined }
]);
});

it('should defer router creation until after mounting and configuration', function (done) {
var app = express();
var admin = express();

admin.on('mount', function () {
this.enable('case sensitive routing');
});
app.use('/admin', admin);
admin.enable('strict routing');
admin.get('/Users', function (req, res) {
res.send('users');
});

var mountedRouter = app.listRoutes()[0].router;
assert.strictEqual(mountedRouter, admin.router);
assert.strictEqual(mountedRouter.caseSensitive, true);
assert.strictEqual(mountedRouter.strict, true);

request(app).get('/admin/users').expect(404, done);
});

it('should preserve sub-application dispatch and restore the parent application', function (done) {
var app = express();
var admin = express();

app.request.scope = function () { return 'parent'; };
admin.request.scope = function () { return 'child'; };
app.response.scope = function () { return 'parent'; };
admin.response.scope = function () { return 'child'; };

admin.get('/users', function (req, res, next) {
assert.strictEqual(req.app, admin);
assert.strictEqual(res.app, admin);
assert.strictEqual(req.scope(), 'child');
assert.strictEqual(res.scope(), 'child');
res.setHeader('x-subapp', 'hit');
next();
});
app.use('/admin', admin);
app.use(function (req, res) {
assert.strictEqual(req.app, app);
assert.strictEqual(res.app, app);
assert.strictEqual(req.scope(), 'parent');
assert.strictEqual(res.scope(), 'parent');
res.send('parent');
});

assert.strictEqual(app.listRoutes()[0].router, admin.router);

request(app).get('/admin/users')
.expect('x-subapp', 'hit')
.expect(200, 'parent', done);
});
});

function noop() {}
Loading