Express route wrong match - node.js

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 :(

Related

How do Express router.param and router.route work in terms of defining URL parameter names?

I created a router file using Express. The callback functions reside in their discrete "controllers" files. Following is an excerpt of the parts relevant to my question, and lines such as require of controller functions have been omitted:
const express = require('express');
const router = express.Router();
// This should run first
router.param('coolParamName', validateParamBeforeHandlingReqs);
// Param name is ↑↑↑↑↑ "consumed" here, although NOT defined yet
// This should run after the above code
router.route('/').get(getAllUserNames).post(createUser);
router.route('/:coolParamName').get(getUserName).patch(updateUser).delete(deleteUser);
// Param name is ↑↑↑↑↑ defined here, and was consumed earlier - how?
As the comments explain, it seems like the param name has been defined as coolParamName on the bottom, but "consumed" by the code written above it. This feels strange to me, because I feel it's natural to define first and then use later - is it just me? Did I write a code that's against the intended design pattern?
I would like to understand how Express defines the name of param, and how router.param and router.router handle them.
router.param('coolParamName') essentially registers a callback that will get called for any route (in that router) that uses the :coolParamName parameter and matches the current request. The callback will get called once per request BEFORE the route that matches the request that contains the :coolParamName parameter.
It's kind of like middleware for a matching parameter. It allows you to automatically configure some setup code anytime that particular parameter is matched in a route.
FYI, I expect that router.param() may be one of the least used features of Express since it can be accomplished many other ways, but it probably works best for validation-type code that checks named properties for validity before the route itself gets called.
You could accomplish the same thing by just using a piece of middleware on that specific route too or even just calling a function inside the route handler. So, this is just a nicety feature if you happen to use the same parameter in multiple routes.

A regex route does not pass req.params

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.

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);

Interpreting url path as params

I have faced upon a problem which may sound weird but lot of people must be dealing with.
I have defined two routes in my routes.js file.
Route1
router.route('/atpages/:query')
.get(app.oauth.authorise(), atpagesController.getAtpagesByIdOrName);
Route2
router.route('/atpages/match')
.get(app.oauth.authorise(), atpagesController.matchAtpagesByUrl);
Both the routes are supposed to call different functions in controller, but the issue is whenever i hit Route2 automatically Route1 is being called. I know the issue that it is considering match as a query, my problem is that i need the routes to hit different questions without redefining their endpoints.
What i want is route should remain the same as they are but they should hit respective functions only.
TIA.
Thanks, i resolved the issue.
In my function for route1 i have made a check that if i get query params as match, i am calling the function corresponding to route2.

Expressjs middleware example

Hellow I have code in my app.js, looking like that:
app.use('/someurl', require('./middleware/somemodule'));
-app.use instead app.all
and module looks like:
if(process.env.BLALAL === undefined){
throw "Error: process.env.BLALAL === undefined";
}
module.exports = function(req, res, next){
...
}
is it a bad practice ?
As said on the express api reference:
app.VERB(path, [callback...], callback)
The app.VERB() methods provide the routing functionality in Express,
where VERB is one of the HTTP verbs, such as app.post().
app.use([path], function)
Use the given middleware function, with optional mount path,
defaulting to "/".
The "mount" path is stripped and is not visible to the middleware
function. The main effect of this feature is that mounted middleware
may operate without code changes regardless of its "prefix" pathname.
IMO
The functionality may be nearly the same, but there is an underlying semantic meaning. The routes itself should be set through the app.VERB api, while any middleware should be set through the app.use api.
Normally middlewares modify the request or response objects, or inject functionality from other module that may answer the request, or not.
connect.static is a good example. It could be really an app or an HttpServer by itself, but is injected as middleware on other app object.
I personally don't like require inside other commands then var bla = require('bla');, it makes code much worse readable in my opinion and you did not get anything in return.
I am not sure what was your intention, but if your code depends on environment variable, it is better to throw immediately than later when your route is called. So app.use is better then app.all. But I don't understand why aren't you simply test your condition inside app.js and why you hide it in somemodule.

Resources