Reusing logic in a route, nodejs express - node.js

I have a route that's doing a lot of processing to display a view, DB queries and parsing and that sort of thing.
I'd like to 're-use' the processing of that route that gets displayed in the view. Simply using a res.render of the view will not suffice.
I am having a hard time explaining exactly what I'm after - effectively I am trying to avoid duplicating code.
router.get('/edit/:id', function(req, res) {
..lots of processing...
res.render('pages/campaigns/edit-campaign', {
...vars for handlebars...
});
});
router.all('/add/confirmation', function(req, res) {
... i have the data i need here, but not the processing ...
res.render('pages/campaigns/edit-campaign-new', {
...vars...
});
});

Set up a middleware that do that processing and optionally pass the processing results to handlers, if you want to use those in handlers.

Related

passing multiple collections to the same express route

app.get('/dbpage', function(req, res){
Chapters.find({}, function(err, allChapters){
if(err){
console.log(err);
}else{
res.render('dbpage', {chapters: allChapters});
}
});
});
The code above is a small portion of my project, and it's working fine. Chapters is the collection's name which is part of my books database. But I have another collection (from the same database) that I'm trying to pass through the same route /dbpage. How would add this other collection to the code above? So I can access the information stored in this other collection in the /dbpage route.
I've seen some other answers that are more or less suited for what I'm trying to do, but they all seen overkill for such a simple thing. Any suggestions? Thanks!!
You can't invoke the same route name. If you use duplicate route definitions, the one "listed first" will take precedence. The only time the same route definition is allowed is if you utilize a different HTTP verb... such as POST, PUT, PATCH, etc.
Thus, if you truly want to use the same route, you need to pass query params and then push conditional logic in the route, such as:
app.get('/dbpage', function(req, res){
let {someQueryParam} = req.query;
if(someQueryParam === 'someSpecialValue'){
//... do something different...
} else {
Chapters.find({}, function(err, allChapters){
if(err){
console.log(err);
}else{
res.render('dbpage', {chapters: allChapters});
}
}
});
});
And, you'd invoke it with some endpoint such as:
yourDomain.com:3000/dbPage?someQueryParam="someSpecialValue"
Honestly though I'd advise against introducing conditional logic when at all possible. If possible, just set up another endpoint.

Handle POST request from multiple routes all at once

I am submitting a simple contact form in my website's footer (in footer.pug):
form(method="POST" action="contact_form")
input(type='email' name='ct_email' data-name='ct_email' required)
textarea(type='text' name='ct_message' data-name='ct_message' required)
button(type='submit') Send
Since the form is in a template, and the footer template is used throughout the site, the form can be submitted from various routes:
/contact_form
/route1/contact_form
/route1/de/contact_form
and so on...
So now it seems I have to create a handler for all the possible routes:
router.post('/contact_form', function(req, res, next) {
// ...
}
router.post('/route1/contact_form', function(req, res, next) {
// ...
}
How can I easily handle POST requests from all the routes they may be coming from without writing a handler for each?
You can use absolute path reference in your form and it will always submit to the same route even though the form is in different pages.
Try this
form(method="POST" action="/contact_form")
Notice the action changed from contact_form to /contact_form. When you add /, you start referencing the path as an absolute path to the domain. So now, from all pages, the form will be submitted to http://your-domain/contact-form.
Not entirely sure if this is what you mean, but the first argument to ExpressJS's router (I assume that's what router is doing here) can be an array. So instead of:
router.post('/contact_form', function(req, res, next) {
// ...
}
router.post('/route1/contact_form', function(req, res, next) {
// ...
}
You can just do:
router.post(['/contact_form','route1/contact_form'],function(req,res,next){
//some fancy logic to handle both routes.
})
Of course, this requires that you keep a list of these possible routes. On the other hand, you can follow Dinesh Pandiyan's advice, and just use an absolute path. So instead of page1.html, page2.html, page3.html, etc. all having their own own router (or own entry in your router array), you'd essentially be saying "Go to the domain route, then go to this address".
Each request should be handled in separated functions because each request has its own logic. However if you want
function request(req, res, next) {
// Your logic
}
router.post('/contact_form', request) {
// ...
}
router.post('/route1/contact_form', request) {
// ...
}
Right now, I don't have a way to test this code, but I think that will help you.
Here is yet another potential solution - use an independent function as a route handler.
router.post('/a', handlePost);
router.post('/b', handlePost);
router.post('/c', handlePost);
function handlePost(req, res, next){
// use req.path here to figure out what url was called
}

500 TypeError: listener must be a function at ServerResponse.addListener

Based upon the voted answer events.js:130 throw TypeError('listener must be a function')
I am trying to implement Logging wherein we save every action in DB. To cater this, I was planning to listen to finish or close event of response stream in app.js
I understand that I need to put this code in Express frameworks root folder.Can you please specify where exactly so that I can access listen to this event and access requested URL as well.
Below is the code snippet
app.use(function (req, res,next) {
function afterResponse (req,res,next){
console.log("Response finished from the path "+ req.url);
}
res.on('finish', afterResponse(req,res,next));
});
While the 'finish' event emitted by the response object is not in the documentation, it does exist. However, as an undocumented feature, you may want to avoid it since it could be removed anytime. Without more information about exactly what you are trying to accomplish, it's hard to say whether there might be a better alternative, although I suspect there is. With that caveat, here is the answer to your question.
If you want to capture every response, you need to define a middleware function near the top of your app, before any response has been sent (order matters in Express 4). The example you have above is too complicated; the 'finish' event listener doesn't take parameters. Instead, you can simply do this:
// This example uses ES2015 syntax
app.use((req, res, next) => {
res.on('finish', () => {
console.log(`Response finished from the path ${req.url}`);
});
next();
});
The example above adds the listener before any request is sent, but the listener will only be called after the request is sent. Make sure the listener itself takes no parameters; if you pass req as a parameter as in your example, it will be undefined, which is exactly what you don't want. Just define your litsener in the middleware function itself, and you can access req or res as much as you like.
Alternatively, if you don't want the listener to be redefined on every request, you can access req through the response object itself, which is available as the this context within the listener:
// This example uses ES2015 syntax
app.use((req, res, next) => {
res.on('finish', onFinish);
next();
});
function onFinish() {
console.log(`Response finished from the path ${this.req.url}`);
}

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