A regex route does not pass req.params - node.js

I have the following two routes in an Express router:
router.get("/v|verses", (req, res) => ...
router.get("/v|verses/:book", (req, res) => ....
Why does an invocation of /verses/john route to the first one with req.params an empty object?
It works fine if I don't use a regular expression but have separate routes for /v and /verses.

You need to change to the following /v(?:|erses)?/:book.
From the Express documentation:
Express uses path-to-regexp for matching the route paths; see the path-to-regexp documentation for all the possibilities in defining route paths. Express Route Tester is a handy tool for testing basic Express routes, although it does not support pattern matching.
When /v|verses/:book is evaluated through the Express Route Tester tool, the resulting regex is /^\/v\|verses\/((?:[^\/]+?))(?:\/(?=$))?$/i which will fail due to the way the alteration is used - the regex says, to either patch something that starts with ^\/v\ (a plain /v) OR ends with verses\/((?:[^\/]+?))(?:\/(?=$)) (basically verses/<anything>).
The alteration goes in order and it matches the first thing it finds, so for So with input of "/verses/john" in only matches the first alteration and not the second. You can also see this on Regex101.
One thing that you need to keep in mind is that Express uses an old version of the path-to-regexp library - the Express dependency is 0.1.7 whereas the current package version is 6.1.0. I'm not sure why Express is not using a newer version - the older one doesn't seem to fully support some groupings, so it produces invalid regular expression for them.
One option was to pass in a regular expression directly, so you could go for app.get(/^\/(?:v\|verses)\/((?:[^\/]+?))(?:\/(?=$))?$/, (req, res) => {}) - similar to what SHOULD be generated but done by hand. However, it's not readable and you don't get the mapping of req.params.book, you just get.
Another option is to supply an array of paths: app.get(['/verses/:book', '/v/:book'], (req, res) => {}). This is valid way to map multiple paths. If you wish you could go with that.
Finally, however to fix the syntax, you need /v(?:|erses)?/:book - a v optionally followed by erses or nothing in a non-capturing group. If you use a normal capturing group, then /verses/john produces req.params of type: {0: erses, book: john}. So, with this, you get the correct pattern here /^\/((?:v|verses))\/((?:[^\/]+?))(?:\/(?=$))?$/i. See on Regex101.

Related

Invalid Regular Expressions in Express, '/(new)?' - Invalid group

I have the code
app.get('/(new)?', (req, res) => {}
Express returns a SyntaxError: Invalid regular expression: /^\/(?(?:([^\/]+?)))?\/?$/: Invalid group
I did a little of Troubleshooting and found out that the problem is somehow between the slash and brace. If I do app.get('/e(new)?', (req, res) => {} it works just fine...
How can I get around this? Why does this occur?
Please don't hate me for anything stupid I might have done or not finding something on it...
I am using Express 4.17.1.
Use a RegExp literal instead of encapsulating it in a string:
app.get(/\/(new)?/, (req, res) => {}
It's tough to say exactly why this works over what you had originally, but my best guess is that there you've hit a small edge case in the way that these faux-RegExp patterns are interpreted in Express' route signature parser that is not triggered when expressing the pattern literally this way.
If you're so inclined, you may consider reporting this as an issue in Express' GitHub repository - some of the more involved contributors may be able to pinpoint exactly where in the codebase this pattern causes issues.

Express route declaration

I have 2 routes such as,
router.get("/project/:id", (req,res) => {
console.log(1);
});
router.get("/project/active", (req,res) => {
console.log(2);
})
Whenever I call the /project/active route, /project/:id route is getting called.
Any tips here would help.
Your wildcard route with :id in it is greedy and is matching everything including the /project/active URL.
In this route definition:
router.get("/project/:id", ...)
The :id part is a wildcard. It matches ANYTHING. So, /project/anything will match that, including /project/active. Since routes are matched in Express in the order they are declared, your router.get("/project/:id", ...) route matches first and takes the request.
There are four ways around this:
Don't design or declare conflicting routes that might both match a particular URL. In this particular case, you would change one of your two URLs to something unique such as /project/id/:id and /project/active or /project/:id and /activeproject. Then, these two route handlers will never conflict.
Very carefully order your routes from the most specific to the least specific. In this case, you would put router.get("/project/active", ...) before router.get("/project/:id", ...). That gives the more specific route a chance to match before the wildcard route gets to take the request. But, note that this means that your id value can never be named active.
If your ids are uniquely identifiable and different from strings like active (like suppose an ID is all numbers or always contains some numbers), then you can code a regex for the ID that would only match your id values and would not match regular words like `active.
Code your wildcard route to individually check for things it should not match and call next() to continue routing to other routes.
That happens because you specify a dynamic route before a specific route.
The right one should be:
router.get("/project/active", (req,res) => {
console.log(2);
});
router.get("/project/:id", (req,res) => {
console.log(1);
});
In Node.js, we know something that is called a middleware stack. Basically, your requests will pass through middlewares until something gives out a response. Routers are also examples of middlewares.
In this case, your route assumes that /active is an id, as you defined /:id first before /active. Different things (should be your expected behavior) would happen if you switched the order.
References:
Middleware Stack

Express router order of request executions: `/state/:params` vs `/state/absolute-path`

If I have two REST endpoints:
app.get('/something/:id', ...handlers);
app.get('/something/else', ...handlers);
And I send a request to http://host:port/something/else
Is there a way to make Express router execute the endpoint with absolute path first (/something/else) before executing the one that matches the query params (/something/:id)?
I understand that I can reverse the order of invocation and specify the endpoint with query params last. But logically speaking, absolute path should take priority over query params and I believe that's the default behaviour for Koa.js
Just put the more specific route first and the wildcard route second. Routes are matched in order and the first one that matches handles the request and the others are not then processed. So, put the more specific route for /something/else before the /something/:id and you will see the /something/else route work properly when that's the URL.
// put wildcard route last and more specific route definitions first
// routes are matched in the order they are defined
app.get('/something/else', ...handlers);
app.get('/something/:id', ...handlers);
This does raise the question why you have designed this potential conflict into your URL scheme in the first place. You've essentially overloaded the id namespace and have reserved at least one id value for your own use. This can be managed by careful ordering of the route definitions, but it would generally be better if you didn't have this conflict in your URL design in the first place.
Is there a way to make Express router execute the endpoint with absolute path first ('/something/else') before executing the one that matches the query params ('/something/:id')?
Yes, define the more specific route first.
I understand that I can reverse the order of invocation and specify the endpoint with query params last. But logically speaking, absolute path should take priority over query params and I believe that's the default behaviour for Koa.js
You asked about Express. It matches routes in the order you've defined them. It doesn't try to guess which route it "thinks" you want to match first. It lets you define that exactly via the order of your route definitions.
I don't know Koa.js well, but there is this in the doc for Koa2: Middleware is now always run in the order declared by .use() (or .get(), etc.), which matches Express 4 API.
There are no specific route matching rules for express.js to match the routes.It goes and try to match every registered route with incoming request path and calls route handlers for all matched paths. Thus the following code will work.
app.get('/something/:id', (req, res, next) => {
console.log(`Calling with param ${req.params.id}`);
next(); // if you remove next from here it will not call the rest of the handlers
});
app.get('/something/else', (req, res, next) => {
console.log(`Calling with else`);
next();
});
Output:
Thus the only way to make sure the routes match exactly, define routes in their specific order.
app.get('/something/else', ...handlers);
app.get('/something/:id', ...handlers);

Express route wrong match

I've read up other questions on people's routes mismatching and then ordering the routes solving the problem. I've got this problem where my URL route is being treated as a parameter and then express mismatches and leads to the wrong route. e.g. here are the two routes:
app.get('/byASIN/LowPrice/:asin/:price',function(req,res){});
and
app.get('/byASIN/:asin/:price', function(req, res) {});
Now all works fine but as soon as I take any param out of the first route it matches the route given below which is not what I want.
If I hit /byASIN/LowPrice/:asin/:price everything works fine but as soon as I hit /byASIN/LowPrice/:asin it matches byASIN/:asin/:price and hence calls the wrong function and crashes my server. I would like to have them match explicitly and if /byASIN/LowPrice/:asin is called, respond with some warning e.g. you're calling with one less argument. What am I missing here?
By default express Url parameters are not optinial, this is why
app.get('/byASIN/LowPrice/:asin/:price',function(req,res){});
does not match /byASIN/LowPrice/:asin, because the second parameter is missing.
However you can make a parameter optional by adding a ? to it:
app.get('/byASIN/LowPrice/:asin/:price?',function(req,res){});
this should solve your problem.
Try to define a route for /byASIN/LowPrice/:asin/:price to handle, then use a wildcard to handle everything else.
app.get('/byASIN/LowPrice/:asin/:price',function(req,res){});
app.get('*',function(req,res){});
Express matches the route by the order you insert them. If you have the loosely routes defined first, then express will use that one as the match first. An extreme example would be
app.get('*', function(req, res) {});
If this was defined as the first route, then no other route will be called (if without calling next()).
If you want express to always use the strict one first, then you will need to change the order of your routes by having the strict ones defined before the loosely ones.
It'd be nice if express support priority in the route, which could be a good solution for your problem, but until then, unfortunately, this can be fixed by ordering only :(

How can I allow slashes in my Express routes?

I'm attempting to implement permalinks, in the form /2013/02/16/title-with-hyphens. I'd like to use route parameters. If I try the following route:
app.get('/:href', function(req, res) { });
...then I get a 404, presumably because Express is only looking for one parameter, and thinks that there are 4.
I can work around it with /:y/:m/:d/:t, but this forces my permalinks to be of that form permanently.
How do I get route parameters to include slashes?
It seems that app.get("/:href(*)", ...) works fine (at least in Express 4). You will get your parameter value in req.params.href.
It will also be fired by / route, which is probably not what you want. You can avoid it by setting app.get('/', ...) elsewhere in your app or explicitly checking for an empty string.
Use a regular expression instead of a string.
app.get(/^\/(.+)/, function(req, res) {
var href = req.params[0]; // regexp's numbered capture group
});
Note that you cannot use the string syntax (app.get('/:href(.+)')) because Express only allows a small subset of regular expressions in route strings, and it uses those regular expressions as a conditional check for that particular component of the route. It does not capture the matched content in the conditional, nor does it allow you to match across components (parts of the URL separated by slashes).
For example:
app.get('/:compa([0-9])/:compb([a-z]/')
This route only matches if the first component (compa) is a single digit, and the second component (compb) is a single letter a-z.
'/:href(.+)' says "match the first component only if the content is anything", which doesn't make much sense; that's the default behavior anyway. Additionally, if you examine the source, you'll see that Express is actually forcing the dot in that conditional to be literal.
For example, app.get('/:href(.+)') actually compiles into:
/^\/(?:(\.+))\/?$/i
Notice that your . was escaped, so this route will only match one or more periods.
You can do this with regex routing
app.get('/:href(\d+\/\d+\/\d+\/*)', function(req, res) { });
I don't know if the regex is right, but you get the idea
EDIT:
I don't think the above works, but this does
app.get(/^\/(\d+)\/(\d+)\/(\d+)\/(.*)/, function(req, res) { });
Going to http://localhost:3000/2012/08/05/hello-i-must-be yeilds req.params = [ '2012', '08', '05', 'hello-i-must-be' ]
You can use this if your parameters has include slashes in it
app.get('/:href(*)', function(req, res) { ... })
It works for me. In my case, I used parameters like ABC1/12345/6789(10).
Hopefully this useful.

Resources