getting parameter from default route - node.js

I am getting paramter from default route like this
conf.js: router.use('/' ,require('./hello'));
hello.js:
router.get('/:param', async (req,res)=>{
try{
let { param }= req.params;
console.log(param)
let param = await Params.findOne({param});
if(!param){
return res.redirect('/not-found')
}
})
and when i visit to any url that i am handling or not handling (like this: router.use('/blah',require('./blah'));)
the code above handles it, so how can i prevent that and let the actual handler handle it? for example when i am redirected to /not-found this is actual /not-found handler
router.get('/', async (req,res)=>{
try{
res.render('notfound')
}catch(err){
return res.redirect(url.format({
pathname: '/error',
query: {
message: err.message
}
}))
}
})
but it is handled as /:param

Express routes and middleware are order-dependent. Everying you add to app via use, get, post, etc. is actually one long chain of possible matches for the URL.
If the first route you add matches everything (at least, everything with only one path component) then sure enough, that route is going to match everything.
The solution is to add more specific routes like app.get('/people') first, and catch-alls like app.get('/:param') last.
As for the "not found" case however, I do not understand why you would expect this to fire at all. Of course any one-path-component path will match the /:param route. So perhaps what you really want is to match only certain values of param. For that, Express supports regex rules. There is a good explanation here.

if I understand you , you need to set this route /:param at the end of your routers chain
router.use('/blah',require('./blah'))
//then
router.get('/:param', async (req,res)=>{
try{
let { param }= req.params;
console.log(param)
let param = await Params.findOne({param});
if(!param){
return res.redirect('/not-found')
}
})
so your request will arrive on the first route if it doesn't match it will go to the second one

Related

How to implement custom endpoints in a URL shortener slug

Of all the URL shorteners I have tried, no one allows you to add / is a slug but Rebrandly. E.g: let's say bit.ly/abc, this works, but bit.ly/abc/xyz, this doesn't. But in Rebrandly, rebrand.ly/abc/xyz & rebrand.ly/abc, both work. I tried implementing it on my own, but since we have to use params (:slug), when I visit domain.com/abc/xyz, it says cannot get domain.com/abc/xyz even though, the slug is registered in the DB.
EDIT:
router.get('/:id', async (req, res) => {
try {
const ogDetails = await viewOg(req.params.id)
if (ogDetails === undefined) res.redirect('/')
else if (ogDetails) res.render('view', { og: ogDetails })
} catch (error) {
console.error(error, 'error')
}
})
Here, if the :id, matches something like abc, it works as expected. But let's say it has something like abc/xyz, then I get cannot get path /abc/xyz even though the data is there.
As pointed out by #Evert, using the req.path instead of req.params is the way to go. It will give you the entire path entered.

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

Should my controller take the (res, req) params or already extracted params?

I wonder what way should I organize my routing in expressJS :
Params parsing in Controller
router.get('/users/:id', UserController.get);
class UserController {
get(res, req) {
var id = res.params.id;
UserModel.get(id, function(user) {
res.send(user);
}
}
}
Params parsing in Route
router.get('/users/:id', function(req, res) {
var id = req.params.id;
UserController.get(id, function(user) {
res.json(user);
}
});
class UserController {
get(id, fn) {
UserModel.get(id, fn);
}
}
I find the second version Params parsing in Route easier for
unit test
In case of change in the URL params or request body
but most of the example I found use the first version, why ?
If you consider a much larger, messier real world application, with route names that no longer match controller names etc., it might be beneficial to place the full routing table (all of the router.xxx calls) in one place, such as a routes.js. For a given url, this makes it much simpler for a new developer to figure out which code handles which url.
If you included all of the parameter parsing in your routes.js, it would become really messy and you'd likely lose some of the benefit of having collected all that into one file in the first place.
That said, there's no reason why you cant have the best of both worlds by separating the routing, the parameter parsing/response formatting, and the controller logic each into their own modules.

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