Matching express recursive pattern - node.js

Well, after almost hour of investigation I have to admit I am lost with some basic routing in express 4.
I have a very simple application which is dealing only with nested resources. I have successfully implemented api match as follow:
app.get('/api/:resource*', function(request, response) { ... }
All I need, is basically match any other regexp. I will appreciate any help.

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.

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

Breaking up node module code (for a library/api client)

I'm writing a node module to consume a REST API for a service. For all intents and purposes we might as well say it's twitter (though it's not).
The API is not small. Over a dozen endpoints. Given that I want to offer convenience methods for each of the endpoints I need to split up the code over multiple files. One file would be far too large.
Right now I am testing the pattern I will outline below, but would appreciate any advice as to other means by which I might break up this code. My goal essentially is to extend the prototype of a single object, but do so using multiple files.
Here's the "model" I'm using so far, but don't think is really a good idea:
TwitterClient.js
function TwitterClient(){
this.foo = "bar";
}
require("fs").readdirSync("./endpoints").forEach(function(file) {
require("./endpoints/" + file)(TwitterClient);
});
var exports = module.exports = TwitterClient;
endpoints/endpointA.js etc
module.exports = function(TwitterClient){
TwitterClient.prototype.someMethod = function(){
//do things here
}
}
The basic idea obviously is that any file in the endpoints folder is automatically loaded and the TwitterClient is passed in to it, so that it's prototype can be accessed/extended.
I don't plan to stick with this pattern because for some reason it seems like a bad idea to me.
Any suggestions of better patterns are very much appreciated, cheers

Node.js Express route naming and ordering: how is precedence determined?

Say I've got a few GET routes on my Express application:
// music albums
app.get('/api/albums', routes.albums.getAlbums);
app.get('/api/albums/:id', routes.albums.getAlbum);
app.get('/api/albums/artwork', routes.albums.getAlbumArtwork);
and I attempt to hit them using the follow jQuery AJAX snippet:
$("#retrieveAlbumArtwork").on("click", function() {
$.ajax({
url: "api/albums/artwork",
type: "GET",
data: {
artist: $("#albumArtist").val(),
title: $("#albumTitle").val()
},
// ... callbacks and such
For some reason, this call hits the second handler, with the /:id parameter, instead of the explicit /artwork route. Swapping them like so makes them function as expected:
// music albums
app.get('/api/albums', routes.albums.getAlbums);
app.get('/api/albums/artwork', routes.albums.getAlbumArtwork);
app.get('/api/albums/:id', routes.albums.getAlbum);
Can someone explain exactly why this is happening? I would assume Express would be smart enough to identify an id param (/albums/23453243) versus a querystring (/albums/artwork?artist=artistName&title=albumTitle) and route appropriately, but this doesn't seem to be the case?
No it isn't. :id will match anything. So /api/albums/artwork is totally valid for that match. Express does support RegExp match also. So you could make an explicit numeric matching route using RegExp.
Another option is using app.param as explained in the API documentation here: https://expressjs.com/en/api.html#app.param
This allows you to define matching params for the router so you could have a URL like /api/albums/:albumId where :albumId has to be numeric, you could also validate an albumId at this point if you wished too.
But in all, the second way you are doing it fairly normal, generally I put static routes at the top, then dynamic routes, catch all, then error handlers.

Resources