-
Notifications
You must be signed in to change notification settings - Fork 92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement a faster REST router #282
Changes from 10 commits
200b613
8197ad2
c05b300
44a138e
65c553b
f0a667d
9a1bcef
5638553
ecc5b67
650a707
eff2b94
94166a0
c79b9e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ var async = require('async'); | |
var HttpInvocation = require('./http-invocation'); | ||
var ContextBase = require('./context-base'); | ||
var HttpContext = require('./http-context'); | ||
var RestRouter = require('./rest-router'); | ||
|
||
var json = bodyParser.json; | ||
var urlencoded = bodyParser.urlencoded; | ||
|
@@ -263,6 +264,10 @@ RestAdapter.prototype.createHandler = function() { | |
|
||
var handleUnknownPaths = this._shouldHandleUnknownPaths(); | ||
|
||
// Mount rest-router | ||
var fastRouter = new RestRouter(); | ||
root.use(fastRouter); | ||
|
||
classes.forEach(function(restClass) { | ||
var router = express.Router(); | ||
var className = restClass.sharedClass.name; | ||
|
@@ -277,6 +282,8 @@ RestAdapter.prototype.createHandler = function() { | |
var sharedMethod = restMethod.sharedMethod; | ||
debug(' method %s', sharedMethod.stringName); | ||
restMethod.routes.forEach(function(route) { | ||
// add fullPath for Trie insertion | ||
route.fullPath = joinPaths(restClass.getPath(), route.path); | ||
methods.push({ route: route, method: sharedMethod }); | ||
}); | ||
}); | ||
|
@@ -285,7 +292,7 @@ RestAdapter.prototype.createHandler = function() { | |
methods.sort(sortRoutes); | ||
|
||
methods.forEach(function(m) { | ||
adapter._registerMethodRouteHandlers(router, m.method, m.route); | ||
adapter._registerMethodRouteHandlers(router, m.method, m.route, fastRouter); | ||
}); | ||
|
||
if (handleUnknownPaths) { | ||
|
@@ -414,7 +421,8 @@ RestAdapter.errorHandler = function(options) { | |
|
||
RestAdapter.prototype._registerMethodRouteHandlers = function(router, | ||
sharedMethod, | ||
route) { | ||
route, | ||
fastRouter) { | ||
var handler = sharedMethod.isStatic ? | ||
this._createStaticMethodHandler(sharedMethod) : | ||
this._createPrototypeMethodHandler(sharedMethod); | ||
|
@@ -425,6 +433,11 @@ RestAdapter.prototype._registerMethodRouteHandlers = function(router, | |
// Express 4.x only supports delete | ||
verb = 'delete'; | ||
} | ||
|
||
// if (fastRouter.canRegister(route, handler)) { | ||
handler.methodName = sharedMethod.name; | ||
fastRouter[verb](route, handler); | ||
// } | ||
router[verb](route.path, handler); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moving L422 in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would expect that L422 is not invoked when the method was registered with the new fast router. I'll check. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is a clue - I added some logging to
Please add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the thing is, the handler is registered for all verbs on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could not find this comment on GH, so I am responding here.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, I'm pretty sure about it from express API docs: and it's also the matter of registering the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I understand docs, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bajtos : strong-remoting sorts routes in the following custom way: It first sorts by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I see. And because "all" starts with the first letter in the alphabet, it will end up being registered as the first one. |
||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
|
||
var url = require('url'); | ||
var http = require('http'); | ||
var Trie = require('./trie'); | ||
|
||
var HTTP_METHODS = require('methods'); | ||
|
||
var RestRouter = module.exports = function(options) { | ||
var opts = options || {}; | ||
var router = function(req, res, next) { | ||
router.handle(req, res, next); | ||
}; | ||
|
||
router.__proto__ = RestRouter; | ||
router._trie = new Trie(); | ||
router.options = opts; | ||
|
||
return router; | ||
}; | ||
|
||
// express style API for registering route handlers: ex. route[method](path, handler) | ||
// add verb:handler for path to trie DS | ||
HTTP_METHODS.concat('all').forEach(function(method) { | ||
RestRouter[method] = function(route, handler) { | ||
route.fullPath = normalizePath(route.fullPath, this.opts); | ||
var trie = this._trie; | ||
trie = trie.add(route, handler); | ||
return this; | ||
}; | ||
}); | ||
|
||
// handle request, match path and if found, invoke handler method | ||
RestRouter.handle = function(req, res, next) { | ||
var trie = this._trie; | ||
var verb = req.method.toLowerCase(); | ||
var path = normalizePath(req.url, this.options); | ||
var methods = trie.find(path); | ||
|
||
if (methods && verb in methods) { | ||
methods[verb](req, res, next); | ||
} else { | ||
next(); | ||
} | ||
}; | ||
|
||
// For now: exclude paths with parameters like :id | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^ remove this |
||
// and handler functions with err obj i.e. (err, req, res, next) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this comment still relevant? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe so There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You commented-out |
||
|
||
// RestRouter.canRegister = function(route, handler) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned earlier, we should still preserve this method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to re-write this method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
// return !(route.path.includes(':') && handler.length < 4); | ||
// }; | ||
|
||
function normalizePath(path, options) { | ||
path = url.parse(path).pathname; | ||
if (!options || !(options.caseSensitive)) { | ||
path = path.toLowerCase(); | ||
} | ||
return path; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
|
||
module.exports = Trie; | ||
|
||
function Trie() { | ||
this.methods = {}; | ||
} | ||
|
||
// insert new nodes to Trie | ||
Trie.prototype.add = function(route, handler) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd personally use Trie.prototype.add = function(verb, fullPath, handler) {
// ...
}; That way the |
||
var node = this; | ||
var parts = getPathParts(route.fullPath); | ||
var segment; | ||
|
||
if (!parts.length) { | ||
addVerbHandler(node, route, handler); | ||
return this; | ||
} | ||
for (var i = 0; i < parts.length; i++) { | ||
segment = parts[i]; | ||
if (!(segment in node)) { | ||
node[segment] = new Trie(); | ||
node.param = (segment[0] === ':'); | ||
} | ||
if (i + 1 === parts.length) { | ||
addVerbHandler(node[segment], route, handler); | ||
} | ||
node = node[segment]; | ||
} | ||
|
||
return this; | ||
}; | ||
|
||
// Look for matching path and return methods for a match | ||
Trie.prototype.find = function(path) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bajtos: In our implementation, For the sake of consistency, I've changed the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC I was commenting earlier that I would like this to become There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
var node = this; | ||
var parts = getPathParts(path); | ||
var segment; | ||
|
||
for (var i = 0; i < parts.length; i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. btw this can be simplified to |
||
segment = parts[i]; | ||
if (!(segment in node) && !node.param) return false; | ||
if (node.param) { | ||
segment = Object.keys(node).find(function(key) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if (key[0] === ':') return key; | ||
}); | ||
return node[segment].methods; | ||
} | ||
if (i + 1 === parts.length) return node[segment].methods; | ||
node = node[segment]; | ||
} | ||
}; | ||
|
||
function getPathParts(path) { | ||
return path.trim().split('/').filter(Boolean); | ||
} | ||
|
||
function addVerbHandler(node, route, handler) { | ||
node.methods = node.methods || {}; | ||
var verb = route.verb; | ||
if (node.methods[verb]) { | ||
console.warn( | ||
'WARN: A handler was already registered for %s /api%s : Ignoring the new handler %s', | ||
route.verb.toUpperCase(), | ||
route.fullPath || '/[unknown]', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd personally use |
||
handler.methodName || '<unknown>' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Storing metadata on the handler is not great, can you pass it in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd use
|
||
); | ||
return; | ||
} | ||
node.methods[verb] = handler; | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,93 @@ | ||||||||||||||||||||||||||||||||||||
var assert = require('assert'); | ||||||||||||||||||||||||||||||||||||
var expect = require('chai').expect; | ||||||||||||||||||||||||||||||||||||
var RestRouter = require('../lib/rest-router.js'); | ||||||||||||||||||||||||||||||||||||
var Trie = require('../lib/trie.js'); | ||||||||||||||||||||||||||||||||||||
var express = require('express'); | ||||||||||||||||||||||||||||||||||||
var supertest = require('supertest'); | ||||||||||||||||||||||||||||||||||||
var http = require('http'); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
describe('RestRouter', function() { | ||||||||||||||||||||||||||||||||||||
// Test init | ||||||||||||||||||||||||||||||||||||
it('initializes with Trie datastructure', function(done) { | ||||||||||||||||||||||||||||||||||||
var router = new RestRouter(); | ||||||||||||||||||||||||||||||||||||
expect(router).to.have.property('_trie'); | ||||||||||||||||||||||||||||||||||||
expect(router._trie).to.be.instanceOf(Trie); | ||||||||||||||||||||||||||||||||||||
done(); | ||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// test handle | ||||||||||||||||||||||||||||||||||||
it('handles a request', function(done) { | ||||||||||||||||||||||||||||||||||||
var app = express(); | ||||||||||||||||||||||||||||||||||||
var router = new RestRouter(); | ||||||||||||||||||||||||||||||||||||
var route = { fullPath: '/example', verb: 'get' }; | ||||||||||||||||||||||||||||||||||||
var resBody = 'Hello Wold!'; | ||||||||||||||||||||||||||||||||||||
// this doesn't make sense to me that I have to call router.get(...) | ||||||||||||||||||||||||||||||||||||
// as well as have route.verb = get | ||||||||||||||||||||||||||||||||||||
// it doesn't work without it | ||||||||||||||||||||||||||||||||||||
router.get(route, function(req, res) { | ||||||||||||||||||||||||||||||||||||
res.end(resBody); | ||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||
app.use(router); | ||||||||||||||||||||||||||||||||||||
console.log(router._trie); | ||||||||||||||||||||||||||||||||||||
supertest(app) | ||||||||||||||||||||||||||||||||||||
.get(route.fullPath) | ||||||||||||||||||||||||||||||||||||
.expect(200) | ||||||||||||||||||||||||||||||||||||
.expect(resBody) | ||||||||||||||||||||||||||||||||||||
.end(function(err, res) { | ||||||||||||||||||||||||||||||||||||
if (err) return done(err); | ||||||||||||||||||||||||||||||||||||
done(); | ||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// test handle param | ||||||||||||||||||||||||||||||||||||
it('handles a request with a parameter', function(done) { | ||||||||||||||||||||||||||||||||||||
var app = express(); | ||||||||||||||||||||||||||||||||||||
var router = new RestRouter(); | ||||||||||||||||||||||||||||||||||||
var route = { fullPath: '/example/:id', verb: 'get' }; | ||||||||||||||||||||||||||||||||||||
var id = '1'; | ||||||||||||||||||||||||||||||||||||
// this doesn't make sense to me that I have to call router.get(...) | ||||||||||||||||||||||||||||||||||||
// as well as have route.verb = get | ||||||||||||||||||||||||||||||||||||
// it doesn't work without it | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be fixed. |
||||||||||||||||||||||||||||||||||||
router.get(route, function(req, res, next) { | ||||||||||||||||||||||||||||||||||||
console.log('hit'); | ||||||||||||||||||||||||||||||||||||
res.end(id); | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test always passes, because you always return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In TDD, it's crucial to start with a failing unit test, to see the test fail and fail for the right (expected) reason. Without that, it's easy to write a test that always passed (like this one) or a test that fails for completely different reasons because it's based on wrong assumptions about how the code under test works. Last but not least, seeing the test fail is the opportunity to verify that the failure message makes it clear what went wrong, what was expected and actual result. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand how TDD works. The test was originally failing because I do however agree that it makes sense to test this here as well. |
||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||
app.use(router); | ||||||||||||||||||||||||||||||||||||
console.log(router._trie); | ||||||||||||||||||||||||||||||||||||
supertest(app) | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please simplify the test setup to avoid repetition of the boiler plate setting up an app with a fast router. IIRC, each call of strong-remoting/test/rest-coercion.test.js Lines 30 to 46 in bcc2ad7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good. I never planned on leaving the repeated code in each test. I wanted to make sure I was doing this right before I removed it. |
||||||||||||||||||||||||||||||||||||
.get(route.fullPath.replace(':id', id)) | ||||||||||||||||||||||||||||||||||||
.expect(200) | ||||||||||||||||||||||||||||||||||||
.expect(id) | ||||||||||||||||||||||||||||||||||||
.end(function(err, res) { | ||||||||||||||||||||||||||||||||||||
console.log(res.body); | ||||||||||||||||||||||||||||||||||||
if (err) return done(err); | ||||||||||||||||||||||||||||||||||||
done(); | ||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// test handle param with method after | ||||||||||||||||||||||||||||||||||||
// it('handles a request for a instance method', function() { | ||||||||||||||||||||||||||||||||||||
// var app = express(); | ||||||||||||||||||||||||||||||||||||
// var router = new RestRouter(); | ||||||||||||||||||||||||||||||||||||
// var route = { fullPath: '/example/:id/properties', verb: 'get' }; | ||||||||||||||||||||||||||||||||||||
// var id = '1'; | ||||||||||||||||||||||||||||||||||||
// // this doesn't make sense to me that I have to call router.get(...) | ||||||||||||||||||||||||||||||||||||
// // as well as have route.verb = get | ||||||||||||||||||||||||||||||||||||
// // it doesn't work without it | ||||||||||||||||||||||||||||||||||||
// router.get(route, function(req, res, next) { | ||||||||||||||||||||||||||||||||||||
// // List the properties of a mock object | ||||||||||||||||||||||||||||||||||||
// res.end(req.fullPath); | ||||||||||||||||||||||||||||||||||||
// }); | ||||||||||||||||||||||||||||||||||||
// app.use(router); | ||||||||||||||||||||||||||||||||||||
// console.log(router._trie); | ||||||||||||||||||||||||||||||||||||
// supertest(app) | ||||||||||||||||||||||||||||||||||||
// .get(route.fullPath.replace(':id', id)) | ||||||||||||||||||||||||||||||||||||
// .expect(200) | ||||||||||||||||||||||||||||||||||||
// .expect(id) | ||||||||||||||||||||||||||||||||||||
// .end(function(err, res) { | ||||||||||||||||||||||||||||||||||||
// console.log(res.body); | ||||||||||||||||||||||||||||||||||||
// if (err) return done(err); | ||||||||||||||||||||||||||||||||||||
// done(); | ||||||||||||||||||||||||||||||||||||
// }); | ||||||||||||||||||||||||||||||||||||
// }); | ||||||||||||||||||||||||||||||||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
|
||
var assert = require('assert'); | ||
var expect = require('chai').expect; | ||
var Trie = require('../lib/trie'); | ||
|
||
describe('Trie', function() { | ||
var trie, route; | ||
|
||
describe('trie.add()', function() { | ||
beforeEach(function() { | ||
trie = new Trie(); | ||
route = { fullPath: '', verb: '' }; | ||
}); | ||
|
||
it('has empty root node with only methods key', function() { | ||
expect(trie).to.have.keys('methods'); | ||
}); | ||
|
||
it('registers hanlder for path: / to root node', function() { | ||
trie.add(setRoute(route, '/', 'get'), 'rootHanlder()'); | ||
expect(trie.methods).to.have.keys('get');//{ 'get': 'rootHandler()' }); | ||
}); | ||
|
||
it('create a node for a path with single part, no verb-handler', function() { | ||
trie.add(setRoute(route, '/Planets')); | ||
expect(trie).to.have.property('Planets'); | ||
expect(trie.Planets).to.have.property('methods'); | ||
}); | ||
|
||
it('registers handler for path: /Planets', function() { | ||
trie.add(setRoute(route, '/Planets', 'get'), 'handler()'); | ||
expect(trie).to.have.deep.property('Planets.methods.get', 'handler()'); | ||
}); | ||
|
||
it('registers handler for path: /Planets/count', function() { | ||
trie.add(setRoute(route, '/Planets/count', 'get'), 'count()'); | ||
expect(trie).to.have.deep.property('Planets.count.methods.get', 'count()'); | ||
}); | ||
|
||
it('registers different HTTP verb handlers for the same path ', function() { | ||
trie.add(setRoute(route, '/Planets', 'get'), 'getHandler()'); | ||
expect(trie).to.have.deep.property('Planets.methods.get', 'getHandler()'); | ||
trie.add(setRoute(route, '/Planets', 'post'), 'postHandler()'); | ||
expect(trie).to.have.deep.property('Planets.methods.post', 'postHandler()'); | ||
trie.add(setRoute(route, '/Planets', 'delete'), 'deleteHandler()'); | ||
expect(trie).to.have.deep.property('Planets.methods.delete', 'deleteHandler()'); | ||
}); | ||
|
||
it('While registering more than one handler for one verb+path,' + | ||
' preserves the handler which was registered first', function() { | ||
trie.add(setRoute(route, '/Planets', 'get'), 'getHandler()'); | ||
expect(trie).to.have.deep.property('Planets.methods.get', 'getHandler()'); | ||
trie.add(setRoute(route, '/Planets', 'get'), 'anotherGetHandler()'); | ||
|
||
expect(trie).to.have.deep.property('Planets.methods.get') | ||
.to.not.equal('anotherGetHandler()'); | ||
}); | ||
}); | ||
|
||
describe('trie.find()', function() { | ||
it('should return handler for matching path', function() { | ||
expect(trie).to.have.property('Planets'); | ||
trie.add(setRoute(route, '/Planets/count', 'get'), 'getCount()'); | ||
trie.add(setRoute(route, '/Planets/count', 'post'), 'postCount()'); | ||
var handlers = trie.find('/Planets/count'); | ||
expect(handlers.get).to.be.equal('getCount()'); | ||
expect(handlers.post).to.be.equal('postCount()'); | ||
}); | ||
}); | ||
}); | ||
|
||
function setRoute(route, fullPath, verb) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find this a bit weird. Why can't you simply use a factory function? function aRoute(verb, fullPath) {
return {
verb: verb,
fullPath: fullPath
};
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The above is irrelevant if you pass in |
||
route.fullPath = fullPath, | ||
route.verb = verb; | ||
return route; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's reasonable to expect that fastRouter will support all different route flavours supported by express (e.g. regular expressions). That's why it's important to call
canRegister
(or perhapscanHandle
is a better name?) here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you look at the code for
canRegister
? It only registers with express if the handler function has more than 3 arguments or if there is a ':' in the path (in other words, any route with a parameter).So I just want to be clear and say that you are not suggesting to use the function that exists but instead to make sure that the router can handle the route, otherwise pass it to the express router.
Note: this should be done with an
if else
statement instead of just anif
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct.
👍