The following code is the user-facing part of a new node app we are building:
var loadInvoice = function(req, res, next) {
Invoice.findById(req.params.invoiceId, function (err, invoice) {
if (err) {
res.send(404, 'Page not found');
} else {
req.invoice = invoice;
next();
}
});
};
app.namespace('/invoices/:invoiceId', loadInvoice, function () {
app.get('', function(req, res){
var templateVals = {
//some template data
};
res.render('paymentselection', templateVals);
});
app.post('', function(req, res){
var data = {
// some data for the apiCall
};
someAPI.someRequest(data, function(err, data) {
console.log(res.status());
res.redirect(data.url);
});
});
});
The first method returns a confirmation page where the user presses a button to post to the same url, which triggers a redirect to an external website.
This all works exactly once. Every second request will crash the app with the message Cant set headers after they are sent. After carefull inspection of the code I could find no reason for this to happen so I added the console.log line which indeed confirms the location header has been set. But it is set to the value i got from someAPI on the previous request not the current one.
This makes absolutely no sense to me. I do not store this value anywhere nor do I do caching or persistence of this data in any way.
Does anybody know what could be causing this?
I use express, express-namespace, mogoose and swig
I found out the problem was being caused bij the 'Restler' libaray used within 'someAPI'. I have no idea how this is possible but swapping it out with something else fixed the problem.
Related
I have a working template engine (pug) to fill it's website with content depending from the situation. Acutally that template is rendered for the site '/show'.
Now I also need to change the url of this website depending from the content. That means I need the same template with new content for sites like: '/tree', '/house', '/urban' an do so. '/show' is the starting point, I need to change it's url with the new content.
I'm sure there is an easy answer, but I can't find the fitting question for that. So I can't find the right answer per searchengine. (Express.js res.render() and res.redirect() was my closest success, but it is not helpful for me.
I know, the following code is incorrect, at least because of the two resp.
server.get('/show', (req, resp) => {
loadContent(function(err, content){
if(content){
resp.location('/tree');
resp.render('myTemplate', content);
} else{
console.log(err);
}
})
});
How can I send my content to the template and replace the url to see both on the browser?
to send data to your pug template with express js use this syntax
const router = require('express').Router();
server.get('/show', (req, res, next) => {
loadContent(function(err, content){
if(content){
res.render('myTemplate', { content: content });
} else{
console.log(err);
}
})
and you will get it
script.
var content = !{content};
Well, I've found my problem. My approach was incorrect.
With
server.get('/:kindOfSite', function(req, resp){...});
I'm able to load the same template for different sites.
Learning can get hard sometimes...
Remember that your express route handlers are just functions. There's nothing that forces you to use an anonymous function. You can just use a regular function:
function handleRequest (req, resp) {
loadContent(function(err, content){
if(content){
resp.location('/tree');
resp.render('myTemplate', content);
} else{
console.log(err);
}
})
}
server.get('/show', handleRequest);
server.get('/tree', handleRequest);
server.get('/house', handleRequest);
server.get('/urban', handleRequest);
Indeed, you can do a bit of metaprogramming and call server.get() in a loop:
['/show','/tree','/house','/urban].forEach(route => {
server.get(route,handleRequest)
});
In fact, Express accepts regular expressions as route paths:
server.get(/\/(show|tree|house|urban)/, (req, resp) => {
loadContent(function(err, content){
if(content){
resp.location('/tree');
resp.render('myTemplate', content);
} else{
console.log(err);
}
})
});
My app works as follows:
for--> /anyOtherRoute --->error page
But I want the following operation: for--> /speak/goat(which is not in my database/objects) -->error page
var animals={
pig: "Oink",
cow: "Moo",
dog: "Woof"
};
app.get("/speak/:animal",function(req,res){
//HERE IT WILL CHECK IF THE ANIMAL IS AN OBJECT
//if not get request to the error page should be sent
var animal=req.params.animal;
var sound=animals[animal];
res.send("The "+animal+" says "+sound);
});
I tried sending the get request inside the get request of the /speak/:animal like
/speak/anything other than :animal from objects but it didn't work.
Error page refers to:
app.get("*",function(req,res){
res.send("Sorry ERROR 404");
});
The first thing you need to recognize is that an error page is no different than any other page. Knowing this, you can send back the error response from within your example:
var animals = {
pig: "Oink",
cow: "Moo",
dog: "Woof",
human: "Hello"
};
app.get("/speak/:animal", function(req, res) {
var animal = req.params.animal;
var sound = animals[animal];
if (!sound) { // Check if sound got set to a truthy value
res.status(404).send("Sorry ERROR 404"); // Note call to `status` to send actual 404
return; // Stop function execution without trying to send sound
}
// We will only get here if we didn't `return` earlier
res.send("The "+animal+" says "+sound);
});
app.get("*", function(req, res) {
res.status(404).send("Sorry ERROR 404");
});
Now we don't want to be repeating that error message in multiple places. What if we add a bunch more similar routes and then decide we want to change what the error page looks like? We'll have to change it many different places. To clean this up, we can add a function that sends an error response for us so that we only define the message in one spot.
var animals = {
pig: "Oink",
cow: "Moo",
dog: "Woof",
human: "Hello"
};
function notFound(req, res) {
res.status(404).send("Sorry ERROR 404");
}
app.get("/speak/:animal", function(req, res) {
var animal = req.params.animal;
var sound = animals[animal];
if (!sound) { // Check if sound got set to a truthy value
notFound(req, res); // send the error message
return; // Stop function execution without trying to send sound
}
// We will only get here if we didn't `return` earlier
res.send("The "+animal+" says "+sound);
});
app.get("*", notFound);
// The line above is a more concise way of writing:
// app.get("*", function(req, res) {
// notFound(req, res);
// });
Disclaimer, none of this code is tested and although I proof-read it, it could be broken.
I would like to get some help with the following problem. I'm writing my bsc thesis, and this small part of code would be responsible for registering a user. (I'm new at nodejs actually). I'm using express and mongoose for this too.
I would like to process the request data, and check for some errors, first I would like to check if all fields exist, secondly if someone already registered with this e-mail address.
Based on the errors (or on success), I would like to send different responses. If a field is missing, then a 400 Bad request, if a user exists, then 409 Conflict, and 200 OK, if everything is ok. But I would only like to do the callback if there are no errors, but I'm kinda stuck here... I get the error Can't set headers after they are sent, which is obvious actually, because JS continues processing the code even if a response is set.
app.post('/register', function (req, res) {
var user = new User(req.body);
checkErrors(req, res, user, registerUser);
});
var registerUser = function(req, res, user){
user.save(function(err, user){
if (err) return console.log(err);
});
res.sendStatus(200);
};
var checkErrors = function(req, res, user, callback){
var properties = [ 'firstName', 'lastName', 'email', 'password', 'dateOfBirth' ];
for(var i = 0; i < properties.length; i++){
if(!req.body.hasOwnProperty(properties[i])){
res.status(400).send('field ' + properties[i] + ' not found');
}
}
var criteria = {
email: req.body.email
};
User.find(criteria).exec(function(err, user){
if(user.length > 0){
res.status(409).send('user already exists');
}
});
callback(req, res, user);
};
I think the problem is in the for loop in checkErrors. Since you call res.status(400).send() within the loop, you can end up calling it multiple times, which will trigger an error after the first call since a response will already have been sent back to the client.
Inside the loop, you can instead add missing fields to an array, then check the length of the array to see if you should respond with a 400 or continue. That way, you will only call res.status(400).send() one time.
For example:
...
var missingFields = [];
for(var i = 0; i < properties.length; i++){
if(!req.body.hasOwnProperty(properties[i])){
missingFields.push(properties[i]);
}
}
if(missingFields.length > 0) {
return res.status(400).send({"missingFields" : missingFields});
}
...
In general, I advise that you put return in front of each res.send() call, to ensure that no others are accidentally called later on.
An example of this is:
User.find(criteria).exec(function(err, user){
// We put return here in case you later add conditionals that are not
// mutually exclusive, since execution will continue past the
// res.status() call without return
if(user.length > 0){
return res.status(409).send('user already exists');
}
// Note that we also put this function call within the block of the
// User.find() callback, since it should not execute until
// User.find() completes and we can check for existing users.
return callback(req, res, user);
});
You probably noticed that I moved callback(req, res, user). If we leave callback(req, res, user) outside the body of the User.find() callback, it is possible that it will be executed before User.find() is completed. This is one of the gotchas of asynchronous programming with Node.js. Callback functions signal when a task is completed, so execution can be done "out of order" in relation to your source code if you don't wrap operations that you want to be sequential within callbacks.
On a side note, in the function registerUser, if user.save fails then the client will never know, since the function sends a 200 status code for any request. This happens for the same reason I mentioned above: because res.sendStatus(200) is not wrapped inside the user.save callback function, it may run before the save operation has completed. If an error occurs during a save, you should tell the client, probably with a 500 status code. For example:
var registerUser = function(req, res, user){
user.save(function(err, user){
if (err) {
console.error(err);
return res.status(500).send(err);
}
return res.sendStatus(201);
});
};
Your call to registerUser() is defined after the route and would be undefined since it's not a hoisted function.
Your use of scope in the closures isn't correct. For your specific error, it's because you're running res.send() in a loop when it's only supposed to be called once per request (hence already sent headers a.k.a. response already sent). You should be returning from the function directly after calling res.send() as well.
When this code hits the redirect line it throws the 'Can't set headers after they are sent error' and doesn't redirect. I'm guilty of not fully understanding headers and how express works with them. This link about this error is confusing me a bit, probably because I don't have a basic enough understanding of what's going on. Also, I know this is a bit of a naive approach to authenticating, but I'm just trying to get basic things to work.
app.post('/api/login', function(req, res) {
if (req.body.password === auth.password) {
auth.date = new Date()
res.redirect('/admin')
} else {
console.log("wrong pw")
}
})
UPDATE : thank you to #Brendan Ashworth I missed an obvious else, which I've added now and no longer get the error.
However this line doesn't change the contents of my page
res.sendfile('./public/admin/views/tunes.html')
It worked before I wrapped it with the auth check
var auth = require('../config/auth')
module.exports = function(app) {
/*
* CONTENT API
*/
//...
/*
* Admin Routes
*/
app.get('/admin/login', function(req, res) {
res.sendfile('./public/admin/views/login.html')
})
app.post('/api/login', function(req, res) {
if (req.body.password === auth.password) {
auth.date = new Date()
res.redirect('/admin')
} else {
res.json({message: 'Wrong password!'})
}
})
app.get('/admin', function(req, res) {
if (auth.date) {
res.sendfile('./public/admin/views/tunes.html')
console.log("test") //
} else { //added else
res.redirect('/admin/login')
}
})
app.get('/admin/:url', function(req, res) {
if (auth.date) {
res.sendfile('./public/admin/views/' + req.params.url + '.html')
} else { //added else
res.redirect('/admin/login')
}
})
// frontend routes
// route to handle all angular requests
app.get('*', function(req, res) {
res.sendfile('./public/views/index.html')
})
}
FINAL UPDATE!! The last thing I needed was to handle the redirect client side after sending the file. Simple authentication works perfectly now!
$http.post('/api/login', $scope.auth).success(function() {
window.location.href = '/admin'
})
An explanation of the error Can't set headers after they are sent error:
All HTTP responses follow this basic structure:
.. Response Line ..
.. Headers ..
.. Body ..
If you want to redirect a user, first the Response Line will be sent with a redirect code (lets say 300), then the Headers will be sent with a Location: xxx header.
Then, we can finally send a body (not in the case of a redirect, but in general). However - in the case with your code - you are sending a Body response then trying to redirect the user. Since the headers (and response line) have both already been sent (because you sent the body), it can't send more headers after the body.
An example of this in your code would be:
app.get('/admin', function(req, res) {
if (auth.date) {
res.sendfile('./public/admin/views/tunes.html')
}
res.redirect('/admin/login')
})
If I'm assuming right, you actually want to return after the res.sendfile() call. If auth.date is truthy, then you'll be sending a file (i.e. body response) and then giving a redirect code - that doesn't work.
after redirect just call res.stop();
Background
Yes, there are a lot of different Node.js logging library winston, bunyan and console.log. It's easy to log down the information of the specific request when it has called and when and what information would be in response.
The problem
The problem begins with the sub function calls. When under one request your calling multiple functions which also uses the same logging, how would you pass the request meta - data to these log calls (function parameters seems to be one possible way but these are really messy) ?
Example
Small visual for coders:
// Middleware to set some request based information
app.use(function (req, res, next) {
req.rid = 'Random generated request id for tracking sub queries';
});
app.get('/', function (req, rest) {
async.series({
'users': async.apply(db.users.find),
'posts': async.apply(db.posts.find),
}, function (err, dbRes) {
console.log('API call made ', req.rid)
res.end(dbRes);
});
});
// Now the database functions are in other file but we also need to track down the request id in there
(db.js)
module.exports = {
users: {
find: function () {
console.log('Calling users listing ', req.rid); // ERROR this is not possible to access, not in this scope
// Make query and return result
}
},
posts: {
find: function () {
console.log('Calling post listing ', req.rid); // ERROR this is not possible to access, not in this scope
// Make query and return result
}
}
};
You can log your requests with simple conf in your app.js with;
app.use(function(req, res, next){
console.log('%s %s', req.method, req.url);
next();
});
However, you need to provide logs for specific functions in your controller.