I want to redirect incoming request to different endpoints based on a query parameter in the request.
For example,
http://localhost:9000/?category=xyz¶m1=...¶m2=...
category value can be one of [abc, ijk, pqr, xyz].
Based on category value, request should get redirected to one of "/abc", "/ijk", "/pqr", "/xyz" preserving remaining parameters.
So, the above request should become /xyz/?param1=...¶m2=...
I am new to Nodejs/express, I thought it is better to create different routes based on category value to modularize the code.
What is the ideal approach to handle such cases?
Looks like the old "front page controller" pattern:
app.get('/', routeByParameter)
function routeByParameter(req, res){
var category = req.params.category
if(category === 'abc'){
return abcRoute(req, res)
}
else if(category === 'xyz'){
return xyzRoute(req, res)
}
}
function abcRoute(req, res){
req.send('abc')
}
OR to use the "front page terminology"
app.get('/', dispatcher)
function dispatcher(req, res){
var category = req.params.category
if(category === 'abc'){
return viewAbc(req, res)
}
else if(category === 'xyz'){
return viewXyz(req, res)
}
}
function viewAbc(req, res){
req.send('abc')
}
Alternatively, you could get in and modify or create a custom route dispatcher in the middleware...
Create one route for each of these values [abc, ijk, pqr, xyz]. For handling different query parameters (?category=xyz) create a middleware that transform the request url:
app.use(function(req, res, next) {
// adding category in the url path will redirect to the
// correct route after calling next function
req.url = "/" + req.query.category;
next();
});
Assuming it's a GET request the categories routes would be:
app.get('/abc', function(req, res) {
//do stuff for 'abc' category
res.send('abc');
});
app.get('/ijk', function(req, res) {
//do stuff for 'ijk' category
res.send('ijk');
});
app.get('/pqr', function(req, res) {
//do stuff for 'pqr' category
res.send('pqr');
});
app.get('/xyz', function(req, res) {
//do stuff for 'xyz' category
res.send('xyz');
});
Note that middleware will edit url for all requests, filter by the request url adding the path app.use("/path", function(req,res,next)... or add some logic inside it.
Related
I have two separate app.get routes that render "/" and contain mongoose find{} methods, which relates to two mongodb collections. I then have corresponding post routes that redirect/post to "/" with the data I entered on the forms.
Depending on the form I use (devportals/new or writingportals/new) to submit my data, I would like it to use the correct app.get to find the data and then populate my index.ejs file to display the new entry.
When I submit my post, it always defaults to the devportal.find{} GET route at the top even though I submitted my update to mongodb through the writingportals form.
app.get("/", function (req, res){
devportal.find({}, function(err, newDevPortal){
if(err){
console.log(err);
}else{
res.render("index",{newDevPortal: newDevPortal});
}
});});
app.get("/", function (req, res){
writingportal.find({}, function(err, newWritingPortal){
if(err){
console.log(err);
}else{
res.render("index",{newWritingPortal: newWritingPortal});
}
});
});
app.get("/portals/writingportals/new", function(req, res){
res.render("portals/writingportals/new");
});
app.get("/portals/devportals/new", function(req, res){
res.render("portals/devportals/new");
});
app.post("/", function(req, res){
var wpTitle = req.body.writingtitle;
var wpUrl = req.body.writingurl;
var wpImageUrl = req.body.writingimageurl;
writingportal.create({title: wpTitle, url: wpUrl, imageurl: wpImageUrl}, function(err, newWritingPortal){
if(err){
console.log(err);
}else{
res.redirect("/");
}
});
});
app.post("/", function(req, res){
var devTitle = req.body.writingtitle;
var devUrl = req.body.writingurl;
var devImageUrl = req.body.writingimageurl;
devportal.create({title: devTitle, url: devUrl, imageurl: devImageUrl}, function(err, newDevPortal){
if(err){
console.log(err);
}else{
res.redirect("/");
}
});
});
I have two separate app.get routes that render "/"
Your endpoints do not render "/", they get triggered based on the request uri, which in your case is "/".
Next, you cannot have two endpoints with the same route and same type of request, this is true with any web server. So in your case, you have two endpoints defined for GET requests to '/'. There is no way for your code to understand the difference between those two routes, so it will always hit the first one it finds.
Finally I'll add that for your solution, you most definitely will want unique endpoints because you are taking a template with a data model and sending back markup.... basically an endpoint for each page.
I will say that I'm not a huge fan of that approach. I think it makes life easier using a framework like react, then have every request to '/' return index.html with a reference to your react code... instead of the whole SSR deal.
However, for a lot of things, its simple to just use query parameters in the request. Here is an example where one endpoint is defined that will return all data from any collection through query parameters:
app.get('/api/collection', function(req, res) {
var collection = req.query.collection;
var _collection = db.collection(collection);
_collection.find({}).toArray(function(err, docs) {
if (err)
return res.status(500).send(err);
res.send(docs);
})
})
And then the request url would be GET -> /api/collection?collection=NAME_OF_YOUR_COLLECTION
I have two resources, employees and employee groups. I'm trying to implement a nice URL structure like:
GET /employees List employees.
GET /employees/123 Get employee 123.
GET /employees/groups List employee groups.
GET /employees/groups/123 Get employee group 123.
Using ExpressJS I have:
router.get('/employees', (req, res, next) => { next(); });
router.get('/employees/:id', (req, res, next) => { next(); });
router.get('/employees/groups', (req, res, next) => { next(); });
router.get('/employees/groups/:id', (req, res, next) => { next(); });
router.all('*', (req, res) => { res.send('...'); });
This doesn't work, because Express can't tell the difference between /employees/:id and /employees/groups. It thinks groups is an id because /employees/:id comes first.
I did have URL's like:
GET /employees
GET /employees/123
GET /employees-groups
GET /employees-groups/123
Which works, but doesn't have the nice resource/sub-resource format. The groups are groups of employees and so I'd like the URL's to match that.
If I were getting the groups for an employee it would be fine (/employees/:id/groups), but I'm getting all groups, which are employee groups.
How could I set up Express routes to route properly while still keeping the URL structure I want..?
I guess I need a way for Express to distinguish between an id and a sub-resource. Is there any way to do that..?
UPDATE
I obviously should've said that I'm using next() in each handler, because I need Express to move onto another middleware function, one that controls the response of all requests. It's this other middleware function that actually sends a response. So I need:
Handler for the route.
Handler for all requests.
Express searches for the first route that matches and handles it with the provided function.
Try the other way around:
router.get('/employees', (req, res) => {});
router.get('/employees/groups', (req, res) => {});
router.get('/employees/groups/:id', (req, res) => {});
router.get('/employees/:id', (req, res) => {});
Now express will work its way trough the routes, '/employees/123' will only match on the last route, so that one will be used by express. '/employees/groups' will be matched sooner by the second route and that one will be used.
Very simple but these things can cost you some time figuring out.
RobbyD set me on the right track. This is what I've ended up with:
index.js
router.all('*', setupHandler);
router.get('/employees', getEmployees);
router.get('/employees/groups', getGroups);
router.get('/employees/groups/:id', getGroup);
router.get('/employees/:id', getEmployee);
router.use(errorHandler);
setupHandler()
function setupHandler(req, res, next) {
res.locals.standardRes = {
"some": "data"
};
res.locals.doResponse = (res) => {
// ...
res.json(res.locals.standardRes);
};
next();
}
getEmployees()
function getEmployees(req, res, next) {
somethingThatReturnsAPromise().then(data => {
// add to res.locals.standardRes here
res.locals.doResponse(res);
}).catch(err => {
next(err);
});
}
errorHandler()
function errorHandler(err, req, res, next) {
console.log('err', err);
// add to res.locals.standardRes here
// set correct res.status here
res.locals.doResponse(res);
}
So the handlers are in the order in RobbyD's answer. I've used res.locals to hold a response function (doResponse(res)) to call from each handler. If there's an error I call next(err) as normal to move to errorHandler().
I guess it's all about getting the right flow from middleware to middleware and sending the response at the right time.
Each piece of this code works individually but when put together, the Article section (third set) will not load. I am fairly new to coding so I was hoping for someone to point me in the right direction. I assume this is because the first route is executed and it never hits the second route...but I don't know how to fix it.
// Adds "www." to all URLs
app.all('*', function(req, res, next) {
if (req.headers.host.match(/^www/) == null) {
res.redirect('http://www.' + req.headers.host + req.url);
} else {
next();
}
});
// Serves the .html file but displays without .html in the URL
app.get('*', function(req,res){
var pages = ['/now', '/home', '/itinerary','/exploreourroots','/contact','/credits'];
if (pages.indexOf(req.url.toLowerCase()) !== -1) {
res.sendFile(__dirname + '/public' + req.url + '.html');
};
});
// Loads Articles from the database on the home page
app.get('/home', function(req, res) {
var query = 'SELECT a.title,a.author,a.cover_photo,a.html_loc,a.date_published,c.name country_name FROM articles a INNER JOIN countries c ON c.id=a.country_id ORDER BY a.date_published desc;'
connection.query(query, function(err, rows, fields) {
var list = [];
for (var i = 0;i < rows.length; i++) {
list.push({html_loc: rows[i].html_loc,country_name: rows[i].country_name,cover_photo: rows[i].cover_photo,title: rows[i].title,author: toAuthorPhoto(rows[i].author),date_published: toArticleDate(rows[i].date_published)});
}
res.contentType('application/json');
res.send(list);
});
});
Thanks for the help!
I'm not sure what intentions you have.
What if user type in browser www.mydomain.com/home. You want to return static html file (home.html), which is done by second router. Or you want to serve then article from db (third route)?
If you want to serve article from db when url is /home, then replace order of second and third routes:
app.get('/home', function(req, res) { ... });
app.get('*', function(req,res){ ... });
if you want to serve static html when your route is /home and also you want to have possibility to serve articles, then as before replace orders and additionally change /home router to /article. Then you will serve articles if url is /article :
app.get('/article', function(req, res) { ... });
app.get('*', function(req,res){ ... });
I have this middleware:
app
.use('/:lang?', middleware.setLanguage)
.use('/thanks/:lang?', middleware.setLanguage)
.use('/forum/:lang?', middleware.setLanguage);
I want use a function called setLanguage in every route that have lang?, and currently this code is working, but i can believe dry that.
Someone knows how to? I search in the documentation, but didn't find anything..
Use .param to process the parameters up front so that you don't have to do it on every route that uses it. For example, here's one that starts building a query with moongoose for a product crud:
app.param('product_id', function (req, res, next, product_id) {
req.product_id = new ObjectId(product_id);
req.product = Product.findOne(req.product_id);
next();
});
// update product
app.put('/products/:product_id', function (req, res) {
Product.findOneAndUpdate(req.product_id, req.body, function (err, product) {
res.json(product.toObject());
});
});
// get product by id
app.get('/products/:product_id', function (req, res) {
req.product.lean().exec(function (err, product) {
res.json(product);
});
});
None of my routes have to get the product id and convert it to an ObjectId because i did that up front for all routes with .param.
I am making a wikipedia clone for a project. My initial edit Route looks like this:
router.get('/edit/:id', function(req, res){
var id = req.params.id;
console.log(id);
models.Page.findById(id, function(err, doc){
console.log(doc);
res.render('edit', {page: doc});
});
});
All I am doing is creating an edit page view for a Page with matching id of the id params.
This works until...
I had to add this new route:
router.get('/:url_name/:id', function(req,res){
var id = req.params.id;
models.Page.findById(id, function(err, doc){
res.render('show_page', {page: doc});
});
});
Now when I have this route active, my edit/:id page route doesn't collect the id parameter(req.params.id).
I am lost on why this isn't working and where I should start trying to debug because I am not getting any errors, it will still take me to my edit/:id page, but when I console.log(id) I do not receive a value, or even and undefined, nothing. Furthermore, the {page:doc} I am passing to my edit view is not being found.
If anyone can provide insight or a place to start looking to debug, I'd appreciate it. Just a reminder, the edit/:id route works as it should (req.params.id grabs the id) when I don't have the :url_name/:id route active.
Current Routes -
I added var wiki_routes = require('./routes/wiki'); in my app.js and in that route I have:
// **** URL ROUTES ****
router.get('/', function(req, res) {
models.Page.find(function(err, docs) {
res.render('index', { docs: docs });
});
});
router.get('/:url_name', function(req, res){
var url_name = req.params.url_name;
var isUpdated = req.query.updated;
var updated = (isUpdated === 'true')?true:false;
models.Page.find({url_name: url_name}, function(err, page){
if(page.length > 1){
console.log(page);
res.render('disambiguation', {pages: page, updated: updated });
} else {
console.log(page);
res.render('show_page', {page: page[0], updated: updated});
}
});
});
router.get('/:url_name/:id', function(req,res){
var id = req.params.id;
models.Page.findById(id, function(err, doc){
res.render('show_page', {page: doc});
});
});
// **** EDIT ROUTES ****
router.get('/edit/:id', function(req, res){
var id = req.params.id;
console.log(id);
models.Page.findById(id, function(err, doc){
console.log(doc);
res.render('edit', {page: doc});
});
});
router.post('/edit_submit/:id', function(req, res){
var id = req.params.id;
var new_title = req.body.title;
var new_body = req.body.body;
console.log(req.body);
models.Page.findByIdAndUpdate(id, {title: new_title, body: new_body }, function(err, docs){
// redirects to the wiki page
res.redirect('/wiki/'+ docs.url_name +'?updated=true');
});
});
// **** DELETE ROUTE ****
router.get('/delete/:id', function(req, res){
var id = req.params.id;
models.Page.findByIdAndRemove(id, function(err, data){
res.redirect('/?deleted=true');
});
});
Routes are set up as they occur in the code, as node will give presedence to whichever route it encounters first, so order matters a lot when you're setting up your routes.
An URL could potentially match several routes, especially when using variables that catch a large number of different URL's, or static routes etc.
Say in your case you have an URL that looks like
http://example.com/edit/1234
That URL would most certainly be caught by this route
router.get('/:url_name/:id' ....
as it matches the http://example.com/something/something layout, and it would also be caught by the following route
router.get('/edit/:id', ....
as it matches the http://example.com/edit/something layout.
What route actually cathes the URL depends on the order they where encountered when set up, whichever route that was declared first will catch the URL.
Just shifting the order of the routes will in most cases solve issues like this
// if the URL matches, this will execute first
router.get('/edit/:id', function(req, res){
// do stuff
});
// You'll only get here if the URL doesn't match the above route
router.get('/:url_name/:id', function(req, res){
// do stuff
});
There is a workaround if you simply can't swap the routes around, using the next() callback, like this
router.get('/:url_name/:id', function(req, res, next){
if ( req.params.url_name == 'edit' ) {
next(); // this sends the request back to look for routes below this one
}else{
// do stuff
}
});
router.get('/edit/:id', function(req, res){
// now we'll get here when the "url_name" is "edit" ...
});