next() promise error handling - node.js

What would be the optimal way for error handling?
I need custom json error messages. It's an API.
exports.putCurso = function (req, res, next) {
util.updateDocument(req.curso, Curso, req.body);
req.curso.saveAsync()
.then(function (data) {
return res.status(201).json({message: 'Curso atualizado.', data: data});
})
.catch(function(error) {
return res.status(500).json({message: 'ERROR!'});
//OR return next(error); but I need custom json error messages so it doesn't make sense
})
.finally(next); //OR return next(error)? redundant?
};

I am no mongoose guy but I know one or two things about express and promise
exports.putCurso = function (req, res, next) {
util.updateDocument(req.curso, Curso, req.body);
req.curso.saveAsync()
.then(function (data) {
res.status(201).json({message: 'Curso atualizado.', data: data});
}, function(error){
res.status(500).json({message: 'ERROR!'});
})
};
And this is basically all that you need. Based on the implementation, this is probably a normal route because it always returns something (res.json) to the client. Therefore, you don't have to call next because it is meant for middlewares to call
Also you don't have to return anything because when you call res.json, it basically says that this request ends here, nothing else.
Last but not least, by specification, promise then supports 2 functions, the first one is for handing successful case, the 2nd one is for exceptions. So, you don't have to call catch

Considering Curso a mongoose document
You can do it like this
req.curso.save(function(err,data){
if(err) res.status(500).json({message: 'ERROR!'});
else res.status(201).json({message: 'Curso atualizado.', data: data})
});
EDIT : if you have so many similar issues through out your little huge node application, its worth looking at rb, then you can do it like
var RB = require('rb');
exports.putCurso = function (req, res, next) {
util.updateDocument(req.curso, Curso, req.body);
// the below line could have been written in some middleware (eg middleware provided by express.io), so we do get clear code in controller part.
res.RB = RB.build(res, { // you may customize your builder yours way, after looking into `rb` docs
errorStatus : 500, successStatus : 201,
errorKey : false, successKey : 'data',
preProcessError : function(){ return { message : 'ERROR!' } },
addToSuccess : { message : 'Curso atualizado.' }
});
//Now only one line in controller
req.curso.save(res.RB.all);
};
Disclosure : i am author of rb.

asCallback takes a callback which it calls with the promise outcome mapped to the callback convention:
If the promise is rejected, it calls the callback with the error as first argument: cb(error)
If the promise is fulfilled, it calls the callback with the value as the second argument: cb(null, value).
exports.putCurso = function (req, res, next) {
util.updateDocument(req.curso, Curso, req.body);
req.curso.saveAsync()
.then(function (data) {
return res.status(201).json({message: 'Curso atualizado.', data: data});
})
.catch(function(error) {
return res.status(500).json({message: 'ERROR!'});
//OR return next(error); but I need custom json error messages so it doesn't make sense
})
.asCallback(next);
};

Related

Return out of express route from inside mongoose callback when error

I am trying various ways of handling errors. If I have a setup to add a new document and I want to check that the name is unique, I do something like
router.route('/addSomething').post(async (req,res,next) => {
await Document.findOne(
{name: req.body.name}, // search parameter
(error,doc) = { // callback
if(error) return next(error) // pass system generated error to next() and exit route
else if (doc) return next(new Error("This name already exists")) // pass my own error to next() and exit route
})
// everything from now on shouldn't happen
await Document.save() etc...
The problem I'm having is that the route function continues even though an error has returned (I understand this is because the return statement only returns from the callback function).
I'm looking for an elegant way of handling mongoose/mongodb errors and exiting the function at point of error, without putting try/catch blocks everywhere.
When you return next(...) inside your callback function you're not actually returning from the rest of your code, only inside that function. To fix this issue I have added a continue() function inside of your code to continue the incoming HTTP request.
router.route('/addSomething').post(async (req, res, next) => {
await Document.findOne(
{ name: req.body.name }, // search parameter
(error, doc) => { // callback
if (error) return next(error) // pass system generated error to next() and exit route
else if (doc) return next(new Error("This name already exists")) // pass my own error to next() and exit route
continue(); // Call the continue function if function isn't already returned
}
);
function continue() { // Add a continue function
// Continue your code
await Document.save();
// ...
}
});
Consider using util.promisify to get rid of the callback:
router.route('/addSomething').post(async (req, res, next) => {
try {
var doc = await util.promisify(Document.findOne)(
{ name: req.body.name } // search parameter
);
} catch(error) {
return next(error); // pass system generated error to next() and exit route
}
if (doc) return next(new Error("This name already exists")); // pass my own error to next() and exit route
// everything from now on shouldn't happen
await Document.save() etc...
});

How to resolve next is not a function error message?

I am trying to create a MEAN stack app, I'm currently working on the UPDATE functionality.
My code is currently failing when it runs into this method:
businessRoutes.route('/update/:id').post(function (req, res) {
Business.findById(req.params.id, function (err, business) {
if (!business)
return next(new Error('Could not load Document'));
else {
business.person_name = req.body.person_name;
business.business_name = req.body.business_name;
business.business_gst_number = req.body.business_gst_number;
business.save().then(business => {
res.json('Update complete');
console.log('Update Complete');
})
.catch(err => {
res.status(400).send("unable to update the database");
});
}
});
});
The error message being displayed in the console is:
TypeError: next is not a function
It's failing on this line of code:
return next(new Error('Could not load Document'));
Can someone please tell me why this is occurring & how I can resolve it?
The second parameter to findById expects a callback that has two arguments, err and <entity>. There's no middleware or something else in place, what you call next(...) tries to call your found entity.
From the docs
Adventure.findById(id, function (err, adventure) {});
You see, in your case, business is always undefined, and adventure or next is never a function.
Here is what you probably meant:
The problem is that the param that you mean to be "next" actually comes from express.js (which means it comes in on the first function call back that you ran (after .post(---this function--- has 3 params that you may chose to use:
req the request that the user made to your server
res the response that you are getting ready to send
next the 3rd param is optional and allows you to send to the next middleware or if you send it a parameter it will send it to the error handler which will send your error as a response.
On the other hand:
you placed the next param randomly in the middle of the mongoose function that you attempted to call: the mongoose function actually only takes (err, item)...
businessRoutes.route('/update/:id').post(function (req, res, next) {
// note that express exposes the "next" param
// this is a callback indicating to run the `next` middleware
Business.findById(req.params.id, function (err, business, whoKnows) {
// note this is from mongoose.js: the callback function actually only has 2 parameters (err, and foundObject)
if (!business)
return next(new Error('Could not load Document'));
else {
business.person_name = req.body.person_name;
business.business_name = req.body.business_name;
business.business_gst_number = req.body.business_gst_number;
business.save().then(business => {
res.json('Update complete');
})
.catch(err => {
res.status(400).send("unable to update the database");
});
}
});
});
Be advised, that I'd recommend using async await and that would make the whole thing much easier to understand:
businessRoutes.route('/update/:id').post(async (req, res, next) => {
try {
const foundBusiness = await Business.findById(req.params.id)
//... do stuff
catch (e) {
next(throw new Error(e))
// .. other stuff
}
})

Express error handling between model and routes

I have a router file which handles all the routes for country in express and call a function from Model file.
router.get('/:_id', function(req, res, next){
countriesModel.getCountry(req.params._id, function(data, err){
if(err)
{
res.json({status:0, message:"Country Not Found for id : "+req.params._id, errDetails:{err}});
res.end();
}
else
{
res.json(data);
res.end();
}
}); });
And here is the getCountry Function from model file.
exports.getCountry = function(id, callback){
return db.queryAsync('select * from tbl_countries where ID = '+id)
.then(function(countryRows){
if(countryRows.length){
return Promise.resolve(callback(countryRows));
}
else
{
return Promise.resolve(callback('No Data To Return.'));
}
});
}
It works fine when i enter correct id, however i want to push error when someone enters wrong id which is not available in database.
Can you please guide me how i can achieve this, I am new to Node & Express.
I am using mySQL with express.
First off, since your database is already returning a promise, you can just make your function return a rejected promise when there's an error condition. And, stop using plain callbacks at all. You already have a promise, let your caller use that:
exports.getCountry = function(id){
return db.queryAsync('select * from tbl_countries where ID = '+id)
.then(function(countryRows){
if(countryRows.length){
// make countryRows be resolved value of the promise
return countryRows;
} else {
// make promise be rejected with this error
throw new Error('Country not found'));
}
});
}
Then, in the router, use the returned promise:
router.get('/:_id', function(req, res, next){
countriesModel.getCountry(req.params._id).then(data => {
res.json(data);
}).catch(err => {
res.json({status:0, message:"Country Not Found for id : "+req.params._id, errDetails:{err}});
});
});
Notes:
There are other reasons you can get a rejected promise here (such as some sort of database error). You have to decide what you want to return in your route when that happens. Right now, it returns the country not found for all errors, but those other types of errors should probably return a 5xx status.
There is no reason to call res.end() after a res.send() or a res.json() as it is called automatically for you.
Never mix promises with plain callbacks. If you have a promise already at the lowest level, just use it and don't cover it with a plain callback.

Why am I getting a warning when using express app.param to pre-load object with sequelize?

I'm using express app.param to load objects from db (app.param('userId', users.userById);):
exports.userById = function (req, res, next, id) {
return user.findOne({
where: { id: id }
}).then(function (result) {
req.user = result;
next();
}).catch(function (error) {
next(error);
});
};
After that I update the loaded object with the following code.
exports.update = function (req, res) {
var user = req.user;
return user.update({
//update properties
}).then(function () {
res.end();
}).catch(function (error) {
//error handling
});
};
For some reason I get the warning that "a promise was created in a handler but was not returned from it".
I can't see why, but that always happen when I use a routing parameter that uses sequelize before making the actual changes to the database.
What is the correct way to do this?
I'm using sequelize v3.23.3 with Postgres.
EDIT
I changed the code to a more simple example that throws the same warning.
If you forget to return that request promise, the next handler executes immediately with an argument of undefined - which is completely valid for the Promises/A+ spec, but I don't think that it is what you are looking for.
See How to execute code after loop completes for solutions.

Using Waterline find method with promisses

I'm stuck when it comes to returning response, from method that includes database call.
Here's sample of what I need...
service.js
module.exports = {
getUserStatus: function(userId){
return User
.find()
.where({userId: userId)
.exec(function(err, user){
return user.status;
}
}
}
In this service.js, I should fetch user's status and if I console.log(user.status) inside exec method, that is printed OK (I got status).
The problem is I need this result outside service.js:
controller.js
// this code is extracted from longer file, just for demo purpose.
// userService is required 'service.js'
index: function(req, res) {
var status = userService.getUserStatus(req.session.User.id);
console.log(status);
return res.view({userStatus: status});
}
If I console.log(status) here, it will be undefined.
I guess that it has something to do with promises and stuff (because of the async calls), but not sure what is the right way to do it.
getUserStatus contains asynchronous code, so it needs a callback:
module.exports = {
getUserStatus: function(userId, cb){
User.findOne().where({userId: userId}).exec(function(err, user){
if (err) return cb(err);
return cb(null, user.status);
});
}
}
then in the code that uses it:
index: function(req, res) {
userService.getUserStatus(req.session.User.id, function(err, status) {
// If an error was returned from the service, bail out
if (err) {return res.serverError(err);}
console.log(status);
return res.view({userStatus: status});
});
}
Note the use of findOne instead of find; find will return an array.
An alternative would be to return a promise from the service function, and chain your controller code with .then() and .fail():
module.exports = {
getUserStatus: function(userId, cb){
return User.findOne().where({userId: userId});
}
}
index: function(req, res) {
userService.getUserStatus(req.session.User.id)
.then(function(user) {
console.log(user.status);
return res.view({userStatus: user.status});
})
.fail(function(err) {
return res.serverError(err);
});
});
}
It's a matter of preference, but I think the first method is better especially in your case, since it allows the service call to deliver just the status, rather than the whole user object. In general, getting used to the standard (err, result) callback method in Node will serve you well.

Resources