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

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.

Related

need to account for params being blank in node express app

i have a node/express app, and need to account for if users don't enter a stationId param in the Url. Ive been looking at all sorts of regex built in to express, adding express-validator but couldnt get it to work for middleware. what would be the best way to do this? is the something in express or a third party validator?
The stationId is an mix of letters and numbers 1491TH it is used to call a second API to get information about the station entered in the Url. Im trying to work out how to use a validator to check if the param is blank, or if its not in the format i want.
import express from 'express'
import axios from 'axios'
import { cleanseLocation } from './utils.js'
const PORT = 3000
const app = express()
app.get('/:stationId/asset',(req, res) => {
const stationId = cleanseLocation(req.params.stationId)
const resp = await axios.get(`https://online-api/id/stations/${stationId}`)
res.send(resp)
})
app.listen(PORT, () =>
console.log(`The node API is running on: http://localhost:${PORT}.`)
)
When you define a route like:
app.get('/:stationId/asset', ...)
You are using a wildcard for the first path segment. It will match ANYTHING in the first path segment. So, it will match all of these:
/play/asset
/1238576/asset
/%20/asset
It will not match:
/asset
because that's just one path segment.
If you have specific rules for what is and isn't a valid stationId, you can implement a check for those inside the route handler:
app.get('/:stationId/asset',(req, res) => {
if (some logic to check req.param.stationId) {
res.send('working');
} else {
res.status(404).send("Invalid stationId");
}
});
If you need further help with how to implement stationId checking, you will have to disclose exactly how you would tell if it's a valid stationId or not.
is the something in express or a third party validator?
What mechanism to use for implementing validation depends entirely upon how you determine whether it is or isn't a valid stationId. You would have to explain that algorithm or method for us to help further.
The stationId is an mix of letters and numbers 1491TH it is used to call a second API to get information about the station entered in the Url. Im trying to work out how to use a validator to check if the param is blank, or if its not in the format i want
It already can't be blank. It won't match the route if there's no stationId at all.
So, if you are looking for a sequence of letters and numbers, you can just use a regex and define the route such that it will only match the route definition if you get a legal format for a stationId:
app.get('/:stationId([A-Za-z0-9]+)/asset', ...)
FYI, here's a helpful article with good example on using regex in Express routes. The full doc for what you can do with a regex in a route definition is here in the path-to-regexp documentation which is the module Express uses for this feature.

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.

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

Resources