NodeJS Return Back - node.js

I'm trying to figure out the best way to handle errors with NodeJS. I need to return back (Or display an error and not redirect) if there is an error with either the user signing up or their credit card being accepted. The problem is I get the error "Cannot set headers after they're already set" if I try to use more than one res.redirect. How do I get around this. I've shown the locations where I want to redirect below.
Thanks!
app.post('/quiz', function(req, res) {
var token = req.body.stripeToken; // Using Express
var charge = stripe.charges.create({
amount: 749,
currency: "usd",
description: "Example charge",
source: token,
}, function(err, charge) {
if(err) {
console.log('Did not charge or create error' + err);
// return res.redirect('/');
//I WANT TO RETURN TO HOME HERE IF THERES AN ERROR
//I WANT TO RETURN TO HOME HERE IF THERES AN ERROR
//I WANT TO RETURN TO HOME HERE IF THERES AN ERROR
//I WANT TO RETURN TO HOME HERE IF THERES AN ERROR
}
console.log('charged')
var user = new User({
username: req.body.username,
email: req.body.email,
password: req.body.password,
datapoint: req.body.datapoint
})
user.save(function(err) {
console.log('this is the problem' + ' ' + err)
// return res.redirect('/');
//I WANT TO RETURN TO HOME HERE IF THERES AN ERROR
//I WANT TO RETURN TO HOME HERE IF THERES AN ERROR
//I WANT TO RETURN TO HOME HERE IF THERES AN ERROR
//I WANT TO RETURN TO HOME HERE IF THERES AN ERROR
});
req.logIn(user, function(err) {
if(err) {
console.log(err);
}
console.log('all looks good')
// If everything looks good I want to redirect to the next page
// If everything looks good I want to redirect to the next page
// If everything looks good I want to redirect to the next page
// If everything looks good I want to redirect to the next page
// res.redirect('/jobquiz');
});
});
});

Related

Add variable to a URL (node, express)

I'm using node and express. What I want to do is to do is make a mix of res.render and res.redirect.
Thing is, res.render can only receive a .ejs file and data, and redirect will go to a specific URL. What I need to do is go to a specific URL (e.g. /reviews/new/:id), render a .ejs file and give some data to it.
This is my code. I can't use session or cookies for this project.
This are the routes, user enters to edit a review of some show. If it is a new review, the id of the show is in the URL, if the user is editing one, the ID of the review is on the URL. Either way, if something fails, I have to append something to this URL and send data.
router.get('/new/:id', controller.newReview);
router.post('/store', controller.checkBeforeStoringReview);
router.get('/edit/:id', controller.editReview);
router.post('/update', controller.checkBeforeUpdatingReview);
This is the function to check auth before updating.
checkBeforeUpdatingReview: function(req, res) { // auth before updating review (can't use session or cookies)
console.log(req.body)
DB
.User
.findOne(
{
where : {
email: req.body.email,
},
}
)
.then (function (results) {
if (results[0] != '') {
if (bcrypt.compareSync(req.body.password, results.password)) {
return module.exports.updateReview(req, res, results)
} else { // same as catch
return res.render('reviews/edit/', { // i'm missing the ID (which i have in req.params.id) at the end of the route
id : req.params.id,
errors : "Incorrect username or password",
email : req.body.email,
});
}
}
})
.catch (function (error) {
console.log(error)
return res.render('reviews/edit/', { // i'm missing the ID (which i have in req.params.id) at the end of the route
id : req.params.id,
errors : "An unexpected error happened",
email : req.body.email,
});
})
},
If everything's ok, as seen above, it goes directly to this function
updateReview: function(req, res, results) { // update review
console.log(req.body)
DB
.Review
.update(req.body,
{
where : {
id: req.body.review_id,
}
}
)
.then(function (results) {
return res.redirect('/series/detail/' + req.body.series_id)
})
.catch (error => {
return res.send(error)
})
},
TL;DR: If auth fails, should go back to the review url and send the data that was sent so that the user does not lose it.
So that's it, if I could use sessions/cookies I think I would be able to go back to the last route, but I can't for this.
Thanks in advance!

Can I use res.redirect and res.send simultaneously in Node.js?

I want to print user's info on the landing page after they get logged in. I am using res.redirect to redirect to landing page i.e index1.html after a user get successfully logged in.
Now I want to get user info. Here is my node.js code:
app.post('/login-feedback', function (req, res) {
dbConn.then(function(db) {
delete req.body._id; // for safety reasons
var dd = db.db("customermanagement");
dd.collection('admin').findOne({username: req.body.username, password: req.body.password}, function(err, user){
if(err) {
console.log(err);
}
else if(user){
req.session.user = user;
console.log( req.session.user.username);
res.status(200);
res.redirect('/index1.html');
}
else {
res.redirect('/login1.html');
}
});
});
});
I would love if you will tell me explicitly everything step by step.
If you want to send data to a static file, like an html file, you should use res.render :
res.render('/login1.html', {user: req.session.user.username})

Redirect after error?

I have a set up that let's a user create an account using PassportJS and charges them at the same time using Stripe. This all works.
The issue is this is currently allowing user's to sign up using the same username. the function catches the error in the user.save part. However even though it catches the error it still finishes creating the new user and charging them. I need to return back there. However I have been unable to figure out how to res.redirect or something at that part.
How would I change my code to redirect there if theres an error?
app.post('/quiz', function(req, res) {
var user = new User({
username: req.body.username,
email: req.body.email,
password: req.body.password,
datapoint: req.body.datapoint
})
user.save(function(err) {
console.log('this is the problem' + ' ' + err)
if(err){
// I NEED TO REDIRECT BACK HERE IF THERE IS AN ERROR
// I NEED TO REDIRECT BACK HERE IF THERE IS AN ERROR
// I NEED TO REDIRECT BACK HERE IF THERE IS AN ERROR
// I NEED TO REDIRECT BACK HERE IF THERE IS AN ERROR
// I NEED TO REDIRECT BACK HERE IF THERE IS AN ERROR
}
var token = req.body.stripeToken; // Using Express
var charge = stripe.charges.create({
amount: 749,
currency: "usd",
description: "Example charge",
source: token,
}, function(err, charge) {
if(err) {
console.log(err);
}
console.log('charged')
req.logIn(user, function(err) {
if(err) {
console.log(err);
}
console.log('all looks good')
res.redirect('/jobquiz');
});
});
});
});
use return before res.redirect(). It will stops the execution.
if (err)
return res.redirect('/anywhere');
First of all, you need to unique validate your username so that if there is an attempt to use same username, uniqueness error will be thrown. As you are using mongoose, I suggest npm install mongoose-unique-validator. Set your username as unique. more info.
Secondly, catch the error and redirect them to the current form (the form before submission)
if (err) {
// clear cache to prevent 304 response
res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
return res.redirect('back');
}
'back' is base on value of req.get('Referrer') which in your case the submission form.

How to handle and return errors in an API using NodeJS / Express

Im creating an a API using NodeJS with the express framework and mongodb to store my data.
I have a register function which does 3 main things.
Creates the new.
Creates a token and associates it with a user.
Sends an email.
module.exports.register = function(req, res) {
var input = req.body;
var token = uuid.v4();
// Create a new user
var user = new User ({
username: input.username,
email: input.email,
password: input.password,
active: false
});
user.save(function(err) {
if(err) return res.json({success: false, errors: 'Failed To Create User'});
});
// Create a new Token
var newToken = createToken('new', null, user._id);
// Assign New Token To New User
if(newToken) {
user.tokens.push(newToken._id);
user.save(function(err) {
if(err) return res.json({success: false, errors: 'Failed To Save User Token'});
});
}
// Send Email To User
var mailData = {
from: 'deleted#hotmail.com',
to: input.email,
subject: 'Activate Your Account',
text: 'http://localhost:8080/api/auth/activate/' + token
}
mail.messages().send(mailData, function(err, body) {
if(err) return res.json({ success: false, errors: 'Failed To Send Email' });
});
return res.json({
success: true,
status: 'Successfully Registered User, Check Email To Activate'
});
}
Now even if there are errors whilst creating the user or the token or sending an email. It's always going to return that it successfully registered a user. How can i restructure / handle this better?
I also have the problem where if the email fails to send the user and token will have already been created, how do i solve this issue? Would i just create a resend activation function?
You mention that it's always going to return that it successfully registered a user. It will also send the email even if the token creation failed.
One (not very pretty) way to do it would be to continue with the next step inside the callback function of the previous step:
user.save(function(err) {
if(err) {
return res.json({success: false, errors: 'Failed To Create User'});
} else {
// Create a new Token
var newToken = createToken('new', null, user._id);
// Assign New Token To New User
if(newToken) {
user.tokens.push(newToken._id);
user.save(function(err) {
if(err) {
return res.json({success: false, errors: 'Failed To Save User Token'});
} else {
// Send Email To User
var mailData = {
from: 'deleted#example.com',
to: input.email,
subject: 'Activate Your Account',
text: 'http://localhost:8080/api/auth/activate/' + token
}
mail.messages().send(mailData, function(err, body) {
if(err) {
return res.json({ success: false, errors: 'Failed To Send Email' });
} else {
return res.json({
success: true,
status: 'Successfully Registered User, Check Email To Activate'
});
}
});
}
});
}
}
});
As you can see, it looks like a callback-piramyd-of-doom very fast, but it only sends the success response when all the previous steps have completed.
You should also add the else case when the newToken is not created.
You should remove final return statement (from the end of your code) and return at the correct place inside each callback if there is no error.
If you send your response in the body of the function your callbacks will never get the chance to run. Therefore you must nest your callbacks and only call res.send if you are
returning early due to an error or
if everything is complete.
e.g.
// Create a new user
var user = new User ({
username: input.username,
email: input.email,
password: input.password,
active: false
});
user.save(function(err) {
if(err) return res.json({success: false, errors: 'Failed To Create User'});
// Create a new Token
var newToken = createToken('new', null, user._id);
// Assign New Token To New User
if(newToken) {
user.tokens.push(newToken._id);
user.save(function(err) {
if(err) return res.json({success: false, errors: 'Failed To Save User Token'});
// Send Email To User
var mailData = {
from: 'deleted#hotmail.com',
to: input.email,
subject: 'Activate Your Account',
text: 'http://localhost:8080/api/auth/activate/' + token
}
mail.messages().send(mailData, function(err, body) {
if(err) return res.json({ success: false, errors: 'Failed To Send Email' });
return res.json({
success: true,
status: 'Successfully Registered User, Check Email To Activate'
});
});
});
}
});
Asynchronous alternatives
Unfortunately, with node.js you should get used to and understand callbacks; even if you end up using something else most of the time. The way your code was structured was neater and logical but does not work in node.js because you have to wait for the callbacks to complete before you can return from your function.
However, callbacks are the default but one of the worst mechanisms for handling asynchronous logic. If you want to structure the code differently you have quite a few options. Here are just a couple:
Use promises instead of callbacks.
In your case your database library (mongoose? sequelize?) should have something built in that allows you to write your code like this:
user.save()
.then(function () {
// step 1
})
.then(funciton () {
// step 2
})
.done()
This style of programming is well worth learning and will make your code more readable than callbacks. callbacks vs promises
Use Koa instead of express.
Koa, is the next generation of express written by the same people. It uses generators instead of callbacks which means you can write code that looks more like this:
// this is just an example
var result = user.save();
if (result.error) return res.send({success : false, ...});
user.token = getNewToken();
user.update();
if (result.error) return res.send({success : false, ...});
return res.send({success : true, message : "Good news, no errors"});
Generators/(aka async functions) are the direction Javascript is moving in but there is a learning curve to start using. Behind the scenes there is something very complex going on to make asynchronous code appear exactly like synchronous code. Basically, the functions know how to pause execution until they are required again.
Start with callbacks
Like I say, callbacks are not that nice. However, you should get used to using them. They are the basic building blocks of node.js and it take a while to get comfortable with better alternatives. It's also important to get used to them because otherwise you won't appreciate why the alternatives are better.
Good luck and watch out for callbacks inside loops :)

Sending JSON error from Node.js backend to iPhone frontend: "Error: Can't set headers after they are sent."

I'm new to Node.js. I'm using it as a server backend to an iPhone client. I'm calling a POST with the JSON: {firstname: "bob", email : bob#someemail.com}
The node.js code looks like this (using Express and Mongoose):
var User = new Schema({
firstname : { type: String, required: true}
, email : { type: String, required: true, unique : true}
});
var User = mongoose.model('User', User);
And for the POST,
app.post('/new/user', function(req, res){
// make a variable for the userData
var userData = {
firstname: req.body.firstname,
email: req.body.email
};
var user = new User(userData);
//try to save the user data
user.save(function(err) {
if (err) {
// if an error occurs, show it in console and send it back to the iPhone
console.log(err);
res.json(err);
}
else{
console.log('New user created');
}
});
res.end();
});
Right now, I'm trying to create duplicate users with the same email. I expect this to throw an error due to the "unique" constraint I have on the email -- which it does.
However, the node.js process dies with, "Error: Can't set headers after they are sent."
I would like to be able to send a message back to the iPhone client in scenarios such as these. For example, in the above, I'd like to be able to send back JSON to the iphone saying the result of the new user creation (successful or failed).
Thank you!
It's because the asynchronous nature of your code. The res.end() runs before the callback function of user.save you should put the res.end()inside that callback ( at the end).
this way:
user.save(function(err) {
if (err) {
// if an error occurs, show it in console and send it back to the iPhone
console.log(err);
return res.json(err);
}
console.log('New user created');
res.end();
});
Send your error using an appropriate http status, you have plenty of 4xx to do that.
res.json(420, err);
That way, you will just have to parse the message in your http fetch, with jquery it gives something like :
jQuery.ajax({
...
error: function (xhr, ajaxOptions, thrownError) {
if(xhr.status == 420) {
JSON.parse(xhr.responseText);
}
}

Resources