I am wondering what the proper way is to make a server response in a NodeJS Express app when an internal server error occurs. Here is some simplified code for registering users. The main issue being, if an internal server error happens, I want to log the error, but also respond to the client with a different message. What I have below is what my current solution is, but I feel like I'm not doing it properly (or not following the conventional way). I currently have an async waterfall setup which is called from the route.
//Controller.js
function verifyInputs(user, resCallback, callback) {
//verify user inputs (ie. passwords match)
if (valid) {
callback(null)
} else {
resCallback('whatever was wrong with inputs', 409)
callback('ok')
}
}
function checkIfUserExists(user, resCallback, callback) {
db.getPool().getConnection((err, connection) => {
if (err) {
resCallback('custom response error message', 500)
callback(err)
return
}
var sql = 'SELECT...'
connection.query(sql, (err, results) => {
connection.release()
if (err) {
resCallback('another custom response error', 500)
callback(err)
return
}
if (results.length > 0) {
resCallback('user already exists')
callback('ok')
}
})
)
}
module.exports.registerNewUser(user, callback) {
async.waterfall([
async.apply(user, callback, verifyInputs),
async.apply(user, callback, checkIfUserExists)
],
function(err, reults) {
if (err === 'ok') return
//log error or whatever here
})
}
This register function is called from the routes function:
//Router.js
router.post('/register', (req, res, next) => {
var newUser = //get user data from req
controller.registerNewUser(newUser, (msg, statusCode) => {
res.statusCode(statusCode)
res.send(msg)
})
})
The code above shows how I log the error while responding to the client with a different message. Is this the right or an OK way to do this?
Or maybe I shouldn't use a waterfall at all for this, and do something like this which would give me access to the res object at all stages without multiple callbacks:
router.post('/register', verifyInputs(), checkIfUserExists(), (req, res, next) => {
var newUser = //get user data from req
controller.registerNewUser(newUser, (msg, statusCode) => {
res.statusCode(statusCode)
res.send(msg)
})
})
I'm relatively new to server back end programming, and I am new to NodeJS and Express. I just want to make sure what I am doing the proper.
Related
I want to get this functionality if(thereIsSomeError) //stop executing further. for example if there some error accurs in middleware or in the callback then i don't want to execute callback(in the app.route) and the middleware further
I tried this code. But i'm still getting req.err as true. how can i fix this issue
// My MiddleWare
export let Middleware=()=> {
return (req,res,next)=>{
next()
console.log(req.err) // Problem is here.. i'm still getting req.err(true)
if(!req.err){
db.query(`query`,(error, responseData)=>{
if(error) console.log(error)
db.query(`second query`,{...// send data to the
database})
})
}
}
}
//End point
app.post('/addStudent',Middleware, (req, res) => {
//setting error to true initially
req.err=true;
let data = req.body
db.query(`query `, data.username, (err, d) => {
if (err) return res.json(err)
else {
// since no Error accured so set the error to false
req.err=false;
let q = 'query';
let values = {//data here}
db.query(q, values, (err, data) => {
if (err) return res.status(200).json(err)
else return res.status(200).json({ data })
})
}
})
})
First, a middleware runs BEFORE a request, NOT AFTER. If you set req.err = true in your POST endpoint, IT WILL STAY TRUE, meaning your database call will certainly return an error.
Second, to successfully abort a middleware call, use return. Returning a function stops it immediately. You can choose either to return next(err) to forward the error to the handler, or to use return res.send('Error') to terminate the response in the middleware.
I am getting error code 400 when I try to login through my frontend. When I see my heroku logs I think it send the query to the database but doesn't gets the result back instead gets an error.
This only happens when the server is running for quite a time and is resolved if I just restart the heroku server.
Code for my login endpoint is:
const Login = (req, res) => {
if (!req.session.user) {
try {
const user = req.body;
let sql = `SELECT * FROM users WHERE type_id=?;`;
db.query(sql, user.type_id, async (err, result) => {
if (err) res.status(400).send(err);
if (result[0]) {
if (await argon2.verify(result[0].password, user.password)) {
req.session.user = {
type: result[0].type,
id: result[0].UserId,
name: result[0].full_name,
typeId: result[0].type_id,
};
res.status(200).send("logged in");
} else res.send("wrong password");
} else res.send("invalid id / user doesnot exist sign up first");
});
} catch {
res.status(500).send(null);
}
} else res.send("already logged in");
};
This happens for my root endpoint also :
app.get("/", (req, res) => {
try {
const sql = "SELECT * FROM users;";
db.query(sql, (err, result) => {
if (err) res.send(err);
res.send(result);
});
console.log(req.session);
} catch {
res.status(500).send(null);
}
});
Here when I go to the root from my browser it shows
{"fatal":true}
Again from the heroku logs all I can make out of it is that whenever the query is sent to the database it send back error.
I have some raw json that I'm trying to send to my back end server in mysql. I'm currently trying to loop through the specific array in the json that I need and sending data from each of the children in the array via a POST request but I am getting "Cannot set headers after they are sent to the client".
app.post('/reddit-import', function (req, res) {
console.log("Route /reddit-import POST");
let data = req.body.data.children
data.forEach(child => {
let sql1 = `CALL insert_user('${child.data.author}',
'${child.data.author_fullname}');`
connection.query(sql1,
data,
function (errQuery, result) {
if (errQuery) {
console.log(errQuery);
res.json({status: "Error", err: errQuery});
res.end();
} else {
console.log("Insert ID: ", result.insertId);
res.json({status: result.insertId, err: ""});
res.end();
}
}
);
When I send the POST request, my backend gets 2 rows of data before it hits me with the error message...any ideas?
You seem to be ending your outer response in the data.forEach with a res.end(), which I’m assuming is used to indicate the end of the outer HTTP request to the client. Did you perhaps mean to use “result” there instead?
Try this if you need to keep track insert IDs:
app.post('/reddit-import', function(req, res) {
console.log("Route /reddit-import POST");
let data = req.body.data.children
const insertIds = data.map(child => {
return new Promise((resolve, reject) => {
const sql = `CALL insert_user('${child.data.author}', '${child.data.author_fullname}')`;
connection.query(sql, (err, result) => {
if (err) {
console.log(err);
return reject(err);
}
console.log("Insert ID: ", result.insertId);
return resolve(result.insertId);
});
});
});
return Promise.all(insertIds)
.then(ids => {
return res.json({
insertIds: ids
});
})
.catch(err => {
return res.status(500).json({
message: 'got query error'
});
});
});
What this basically does is that on each query, you keep track of the insert IDs. We need to use Promises because the query() function is asynchronous, meaning it runs independently and there's no other way to keep track of the data outside of its function(err, result) callback. Now we have an array of Promises which contains the insert IDs, and what's left is to send a response that this is successful. And in order to do that, we can't simply do res.json(insertIds) because insertIds is an array of Promises and we still need to extract the values. We can easily extract all data at once from an array of Promises by using Promise.all(insertIds).then(ids => ...). If you wish to send a response informing that the request is successful, do so in this then callback. Lastly and most importantly, we handle errors in a Promise chain's .catch() block. This is where you want to send a response informing the client that there are errors.
Some things that we can improve from this solution is to implement rollbacks in case we have errors, and of course validations of parameters. Unfortunately I have to leave this to the OP to implement.
Also, keep in mind you should only send a response once and only once each request.
Suppose I have executed this query in the nodejs app
var query ="INSERT INTO `Customers` set ?";
conn.query(query, DataSet,function(err,result)
{
if(err)
{
console.error(err);
return;
}else{
console.log(result);
}
});
If any error occurs in the execution of the query I want the error to be displayed on the client side, and if not the success message should be displayed.
So the main issue is how to send the error message of the executed query to the client side
If you're in an express router method you use the res object to push results back to the web client. Perhaps something like this.
const express = require( 'express' );
const router = express.Router();
router.get( '/', function( req, res, next ) {
conn.query(query, DataSet,function(err, result) {
if(err) {
res.status( 500 ).json( { status: 500, message: err } );
}
else {
res.status( 200 ).json( { status: 200, message: result } );
}
});
});
This example tosses back a JSON object with the message in it, and tosses back either 500 (server error) or 200 (success) status. You may need something else. You still use the res object.
I'm making an app with express + passport and angularJS; I want to be able to send any errors produced from passport (such as username taken or no email provided) by json so my angularJS app can receive these errors in a json response. More specifically right now I want to have a json response to my signup POST method that outputs any errors. I have tried to do this for myself and I've search all over the web and stack overflow I just cannot work this out!
Here is my users route file in express:
var express = require('express');
var router = express.Router();
var isAuthenticated = require('../config/isAuthenticated');
module.exports = function(passport){
router.get('/loggedin', function(req, res){
res.send(req.isAuthenticated() ? req.user : '0');
});
router.post('/signup', passport.authenticate('local-signup', {
successRedirect : '/',
failureRedirect : '/signup',
failureFlash: true
}));
router.post('/login', passport.authenticate('local-login'), function(req, res){
res.send(req.user);
});
router.post('/signout', function(req,res){
req.logout();
res.json({redirect: '/'});
});
router.get('/authtest', isAuthenticated, function(req, res){
res.render('authtest', {user: req.user});
});
return router;
};
This is my passport signup strategy:
passport.use('local-signup', new LocalStrategy({
usernameField : 'username',
passwordField : 'password',
passReqToCallback : true
},
function(req, username, password, done){
process.nextTick(function(){
User.findOne({'local.username' : username}, function(err, user){
if(err) return done(err);
if (user) { //username already exists
return done(null, false, {message: 'Username already exists'});
} else if(!req.body.email) { //no email address provided
return done(null, false, {message: 'You must provide an email address!'});
} else {
var newUser = new User();
newUser.local.username = username;
newUser.generateHash(password, function(err, hash){
if(err) return done(err);
newUser.local.password = hash;
});
newUser.email = req.body.email;
newUser.servers = [];
newUser.save(function(err){
if(err) throw err;
return done(null, newUser);
});
};
});
});
}
));
I know looking at my code right now it looks like I haven't tried to solve this myself at all but this is just my latest working code; I have been stuck at this for the past few days!
Any help would be greatly appreciated :)
According to the current code of passport this is probably achievable by passing custom callback to handle all results of authentiction yourself. This callback is given after options or instead of those.
passport( "local-signup", { ... }, callbackFn );
or
passport( "local-login", callbackFn );
This callback is used in all resulting situations of trying to authenticae. It is thus invoked on processing errors like this:
callbackFn( err )
If (all configured) authentications have failed it is called with
callbackFn( null, false, challenge(s), status(es) )
On successfully having authenticated user the callback is invoked like so:
callbackFn( null, user, infos )
with infos optionally provided by strategies.
Now comes the bottom-side: In either situation passport.authenticate() skips usual processing but instantly invokes provided callback to care for the rest. This includes processing of any options passed in call for passport.authenticate() like flashing messages, preparing session and request for containing authenticated user etc.
Since options given passport.authenticate() are never passed into callback there is actually no obvious reason to use both.
When I was stumbling over the very same problem (linking passport-service with angular-js POST request) I declined to consider use of callback a proper solution. This callback isn't documented. And it doesn't even look quite useful for it isn't passing req, res and next to pass any actual request in callback. Thus it makes very little sense to use it at all and I'd expect it to vanish soon or to change its behaviour quite much.
So the second approach was about trying to figure out why there is a problem in AngularJS. Passport is sending plain text Unauthorized in response with status code 401. AngularJS is trying to parse this as JSON and produces Syntax error. The text Unauthorized results from passprt ending response very simply by invoking
res.statusCode = 401;
res.end(http.STATUS_CODES[res.statusCode]);
Thus a proper workaround might try to replace
either text in http.STATUS_CODES though this is having impact on processing further requests and thus isn't preferrable
or res.end() by an overloaded method acting differently if res.statusCode is 401.
Due to affecting any current request, only, I tried the latter. Replaced res.end() might be used to send any text you want:
router.post('/login',
function(req, res, next) {
var _end = res.end;
res.end = function() {
if (res.statusCode === 401) {
return _end('{"status":"Unauthorized"}');
}
return _end.apply(this, arguments);
};
next();
},
passport.authenticate('local-login'),
function(req, res) {
res.send(req.user);
}
);
Alternatively the replaced method might add previously missing response header information on content type, for this was actually causing issues in AngularJS processing that response as JSON by default.
router.post('/login',
function(req, res, next) {
var _end = res.end;
res.end = function() {
if (res.statusCode === 401) {
res.set("Content-Type", "text/plain");
}
return _end.apply(this, arguments);
};
next();
},
passport.authenticate('local-login'),
function(req, res) {
res.send(req.user);
}
);
Finally, either approach is really just a workaround. I think passport is in the need for revising this annoying limitation.