NodeJS - Wrong Express route being triggered - node.js

I have the following routes available for a backend...
admin.js
router.get('/contents', ...); // GET /admin/contents
router.get('/:adminID', ...); // GET /admin/[adminID]
router.put('/:adminID', ...); // PUT /admin/[adminID]
router.get('/', ...); // GET /admin
router.post('/', ...); // POST /admin
.. but in testing, the following:
PUT /admin/contents
triggers the PUT /admin/[adminID] route. But "contents" is not an ID. I understand why this is happening (i.e. it fits into the pattern), but I'm not sure what the best/common solution is to this? Ideally, I'd like it to recognize that "contents" is not an ID, and is in fact just attempting to use an unavailable endpoint.
I could use something like...
router.use('/contents', require('./admin-contents'));
but I'd prefer to limit each top-level endpoint to a single file, opposed to spreading it across so many.
Worst-case scenario, it will look for an admin with ID: "contents", and return "admin not found", but I'd prefer it to return 404, because that is not an available endpoint for /admin.
Edit #1
To clarify, adminID is a mix of letters and numbers, with either occurring in any position in the string. A regex will not work.
Also, the only route for /admin/contents is GET. Having to implement blank routes for all the other methods (PUT, PATCH, DELETE, etc) is not ideal either.

You can provide a regex after a paramenter name in the route, to avoid that scenario.
router.put('/:adminID(\\d+)', (req, res) => {
console.log(req.params.adminID); // I'm a number
});
Now adminID must be a number, otherwise it won't enter the route.
While that's not directly documented on express routing, since express uses path-to-regexp we can see their documentation for this:
And it's documented in Custom Matching Parameters
const regexpNumbers = pathToRegexp('/icon-:foo(\\d+).png')
// keys = [{ name: 'foo', ... }]
regexpNumbers.exec('/icon-123.png')
//=> ['/icon-123.png', '123']
regexpNumbers.exec('/icon-abc.png')
//=> null
UPDATE
Your suggestion of checking for even just one number in a known-length
string should work,
app.put('/:adminID((?:\\w+(?<=\\d+)(?:\\w+)?))', (req, res) => {
// I have at least 1 number
// I can have or not alpha-numeric characters
res.send(req.params.adminID);
});
The regex uses Postive lookbehind assertions which are supported without any flag since Node.js 9.11.2. So if you're using an older version, either upgrade or use the --harmony flag to run it.

You can take advantage of the fact that node interprets handlers in order:
app.put('/admin/contents', (req, res) => res.send('contents'))
app.put('/admin/:adminId', (req, res) => res.send('id'))
When you enter admin/contents, contents is returned, for any other url admin/whatever id is returned.

Related

What to do when NodeJS rest api is sendind status 404 while using parameters?

I am having a strange problem while writing my api. I am using Nodejs and express. The problem occurs when i try to use GET with parameters.
This is my routes code
router.get('/addFriends/:email', (req, res, next) =>{
const email = req.params.email;
UserSchema.find({email: email}, { "friendsPending.emailSender": 1, _id : 0}, (err, data) =>{
if(err){
res.status(404).send(err);
}else{
res.status(200).send(data[0]);
}
});
});
This is my call in Postman : /users/addFriends?email=a
When running this call, server returns 404 status. It happened even when i tested it with another get call.Any comments are appriciated, however other POST and GET calls work normally (they use body not parameters). Thanks
You mixed query params and url params. To make your example working, you need to use /addFriends/my-email#gmail.com instead of /users/addFriends?email=a.
If you need to send emails via query params (everything after ?) use req.query in your controller instead of req.params.email.
This route definition:
router.get('/addFriends/:email', ...);
expects a URL that looks like this:
/addFriends/someEmail
And, you would use req.params.email to refer to the "someEmail" value in the URL path.
Not what you are trying to use:
/addFriends?email=a
If you want to use a URL such as (a URL with a query parameter):
/addFriends?email=a
Then, you would have a route definition like this:
router.get('/addFriends', ...);
And, then you would refer to req.query.email in the route handler. Query parameters (things after the ? in the URL) come from the req.query object.
In Express, route definitions match the path of the URL, not the query parameters.
when you use /addFriends/:param you force the router to match any request tha have a part in path as :param.For example:
/users/addFriends/toFavorates // will **match**
/users/addFriends/toFavorates?email=a // will **match**
/users/addFriends?email=a // will **not** match
if you want to make :param as optional part of url path put a ? after it
/addFriends/:param?
it will tell express route that :param is an optinal part. See this question
express uses path-to-regexp for matching the route paths. read the documentation for more options

Node/express, optional parameters switch route between get and post

I have 2 routes set up for my express server that look very close to each other. They are basically the same url, except one is post and one is get, and the get has an extra route param (which is optional). Right now these seem to work ok, however if I do not add the optional param to the get call, it thinks I'm trying to hit the post. I would like to be able to hit the get call without the passing the second optional param as well. Let me show you what I have so far:
router.param('itemID', (req, res, next, itemID) => {
verbose("itemID=", itemID);
next();
});
router.param('navigationType', (req, res, next, navigationType) => {
if (!navigationType) {
next();
}
verbose("navigationType=", navigationType);
next();
});
router.route('/:itemID/navigations')
.post(controllers.addActivity)
.all(routes.send405.bind(null, ['POST']));
router.route('/:itemID/navigations/:navigationType')
.get(controllers.listActivities)
.all(routes.send405.bind(null, ['GET']));
The routed.send405 method looks like this :
function send405(methods, req, res) {
res.set('Allow', methods.join(','));
res.status(405).json({
message: `Method '${req.method}' Not Allowed.`
});
}
So right now the issue is if I do a get on /blah123/navigations and don't add the /:navigationType variable, it thinks I am trying to hit the post method. I am very new to working with this and would appreciate any help or insight. Thanks!
When you declare a route, say GET /admins/:id, it will match any requests to GET /admins/1 or GET /admins/john. But when you do just GET /admins, it wouldn't be able to find because you haven't declared GET route matching that pattern.
To work with this, you have to specify navigationType is an optional parameter and also place the GET request first followed by the POST, like this.
router.route('/:itemID/navigations/:navigationType?')
.get(controllers.listActivities)
.all(routes.send405.bind(null, ['GET']));
router.route('/:itemID/navigations')
.post(controllers.addActivity)
.all(routes.send405.bind(null, ['POST']));

Single express route to capture all possible params combinations

I want my API to support filtering on the different properties of my mongodb model. The brute force way of which I would use:
app.get('/api/thing/:id', thing.getThingById);
app.get('/api/thing/:name, thing.getThingByName);
app.get('/api/thing/:name/:color', thing.getThingByNameAndColor);
etc. This approach is obviously terrible. How can I add a single route to capture multiple params so that I can return things using something like
exports.getThingByParams = function (req, res, next) {
var query = thingModel.find (req.params);
query.exec (function (err, things) {
if (err) return next (err);
res.send ({
status: "200",
responseType: "array",
response: things
});
});
};
Use the URL query string. It's a set of name/value pairs invented for precisely this use case. It still works great after all these years despite current trends the everything must be in the path portion of the URL instead of the query string because ???. Look at your route - it even says "API" in it. It doesn't need to abuse the path to be "pretty" according to hipsters.
app.get('/api/thing', thing.search);
exports.search = function (req, res, next) {
//Remember any ID values need to be converted from strings to ObjectIDs,
//and there's probably additional sanitization/normalization to do here
var query = thingModel.find (req.query);
query.exec (function (err, things) {
if (err) return next (err);
res.send ({
status: "200",
responseType: "array",
response: things
});
});
};
Then the url to find a red thing named candy would look like
/api/thing?color=red&name=candy
Yup, you can. Just use the most explicit route example:
app.get('/api/thing/:name/:color', thing.getThingByProperty);
and inside getThingByProperty simply test for req.params.YourParam (name or color) and decide what to do. If color is requested but not name, you send in the url as follows:
/api/thing/-/red.
Very common sighting to have null params in routes expressed as dash.
OR, different approach, better pattern more API like (RESTFul) is:
/api/thing/:id that's by id and to offer pattern support
and for different property search use:
/api/things/search?property1=value&property2=value
That way you respect the collection pattern in your API (/api/things) and put the search in the query to keep it flexible. Test inside the search callback for req.query.property1 or property and act accordingly

multiple routes with differents params, calling the same ressource

Is it possible with expressjs to have multiple routes calling the same resource, something like that:
app.get('/users/:user_id', users.getOne)
app.get('/users/:username', users.getOne)
I would like to be able to call users.getOne whichever params (:user_id or :username) is used in the get request.
In the users.getOne function, how can I determine wich one was used and build my query according to it?
exports.getOne = function(req, res){
var queryParams = ? // I need help here
Users
.find(queryParams)
...
Thanks!
Possibly related: express.js - single routing handler for multiple routes in a single line
From express's view, both of those routes will match the same set of request URLs. You only need one of them and you can name it to make more sense:
app.get('/users/:key', users.getOne);
//...
// http://stackoverflow.com/a/20988824/266795
var OBJECT_ID_RE = /^[a-f\d]{24}$/i;
exports.getOne = function(req, res) {
var conditions = {_id: req.params.key};
if (!OBJECT_ID_RE.test(req.params.key)) {
conditions = {username: req.params.key};
}
Users.find(conditions)...
If you end up wanting this pattern in many routes throughout your code base, you can extract it into a /users/:user param and use app.param as per #alex's answer, but encapsulate the code to locate the user and stick it on to req.user so the actual route handler can just assume the user has been properly found and loaded by the time it executes, and 404 handling can be centralized as well.
Those are in fact, from express's view, the same route.
No, they are not. One route has :user_id parameter, another one has :username.
This would be a proper solution:
var OBJECT_ID_RE = /^[a-f\d]{24}$/i;
app.param('user_id', function(req, res, next, value, name) {
if (OBJECT_ID_RE.test(value)) {
next()
} else {
next('route')
}
})
app.get('/users/:user_id', users.getOne)
app.get('/users/:username', users.getOne)
app.param set the prerequisite for the route to be called. This way when user_id matches a pattern, first route gets called, otherwise second one.

Express.js routing with optional param?

I have two situations to get data from DB
To show normal data
http://exampleapp.com/task/{{taskId}}
To edit data via posting
http://exampleapp.com/task/{{taskId}}/?state={{app.state}}
Both url have the same http://exampleapp.com/task/{{taskId}} just a little bit different with last phrase ?state={{app.state}}
I use Express routing as followed:
app.get('/task/:taskId/(?state=:status(pending|cancel|confirmed|deleted))?', routes.task.show);
But I dont know why it does not work ?
For example error: Cannot GET /task/51d2c53f329b8e0000000001 when going to h**p://exampleapp.com/task/51d2c53f329b8e0000000001
Query strings cannot be defined in routes. You access query string parameters from req.query.
app.get('/task/:taskId', function(req, res) {
if (req.query.state == 'pending') { ... }
});
However, if you're modifying a task, this is not the appropriate way to do it. GET requests SHOULD be idempotent: the request SHOULD NOT modify state. That's what POST requests are for.
app.get('/task/:taskId', function(req, res) {
// show task info based on `req.params.taskId`
});
app.post('/task/:taskId', function(req, res) {
// set task `req.params.taskId` to state `req.body.state`
});
You could either have a <form> that posts to the task, or make an ajax request:
$.post('/task/1', { state: 'pending' }, function() { ... });
According to the Express API, you cannot mix RegExp routes with string routes.
You should do something like this (I'm assuming taskId is an integer):
app.get(/^\/task/([0-9]+)/(?state=:status(pending|cancel|confirmed|deleted))?, routes.task.show);
However, I don't see why you cannot only check if req.query.state is defined in your route. It's probably less error prone and easier:
app.get("/task/:taskId", function( req, res, next ) {
if (req.query.state) {
// Do things
}
next();
});
Your problem is that query strings are not considered in routing. You will either have to redesign your urls (ie, include the state into the url itself, instead of the query string) or check the query string in your route handler function.

Resources