Express error handling between model and routes - node.js

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.

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
}
})

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.

Unit testing with Supertest, Mocha & Sinon timing out

I am trying to write a unit/integration test where I want to get a list of things in the database. For not it is only a GET, but these tests needs to extend to POST, PUT & DELETE.
The code I have thus far works fine, I can actually get data from the DB, but as soon as I try to stub out the function which is responsable for making the call to the DB, Mocha times out
1 failing
1) /account_types GET 200 List:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
at null. (C:\Code\JS\general_admin_service\node_modules\mocha\lib\runnable.js:215:19)
I understand the done() callback isn't being called because the code is stuck somewhere, however, I do not understand what I am doing wrong.
I used the following references to get where I am:
Testing With Mocha, Sinon.js & Mocking Request
Lessons learned from unit testing with Sinon.JS
My code is as follows:
The Test:
'use strict';
var expect = require('chai').expect,
request = require('supertest'),
chance = require('chance').Chance(),
server = require('../server'),
sinon = require('sinon'),
select = require('../../helpers/data_access/select');
describe("/account_types", function () {
before(function(done){
sinon
.stub(select, "query_list")
.returns([{id: "test"}]);
done();
});
after(function(done){
select
.query_list
.restore();
done();
});
it('GET 200 List', function (done) {
request(server.baseURL)
.get('/api/v1/account_types')
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.include.keys('result');
expect(res.body.result).to.not.be.null;
expect(res.body.result).to.not.be.undefined;
expect(res.body.result).to.be.an('Array');
expect(res.body.result.length).to.be.above(0);
//expect(select.query_list).to.have.been.calledOnce;
return done();
});
});
});
Restify endpoint:
var select = require('../helpers/data_access/select')
module.exports = function (server) {
var query = "..."
return select.query_list(res, next, db_config, query);
});
};
select.js:
var sql = require('mssql');
module.exports = {
query_list: function (res, next, config, sql_query) {
return query(res, next, config, sql_query, true);
},
query_single: function (res, next, config, sql_query) {
return query(res, next, config, sql_query, false);
}
};
function query(res, next, config, sql_query, isList) {
var connection = new sql.Connection(config);
connection.connect(function (err) {
if (err) {
return on_error(err, res);
}
var request = new sql.Request(connection);
request.query(sql_query, function (err, response) {
connection.close();
if (err) {
return on_error(err, res);
}
if (isList) {
return return_list(res, response, next);
} else {
return return_single(res, response, next);
}
});
});
}
function on_error(error, res, next) {
res.status(500).send(error);
return next();
}
function return_list(res, response, next) {
res.send({result: response});
return next();
}
function return_single(res, response, next) {
res.send({result: response[0]});
return next();
}
What I expect to happen is that because I stub out the query_list function, should I wish to put a console.log(res.body.result); after the expect's I have in place, I should see a return of [{id: "test"}], but it is obviously not getting to that point.
What am I doing wrong?
UPDATE: Added the full select.js file.
As you already make clear in the comments, it's difficult to test code that's deeply nested.
It's usually much better to work with callbacks or promises, so that each piece of your app will handle the part it's responsible for, but not (much) more. So your route handler will handle the request and the response. It's obviously okay to call other functions, like ones that perform database queries, but instead of letting those functions send back a response, you use callbacks that "call back" to the route handler with the query results.
Something like this:
server.get('/api/v1/account_types', function(req, res, next) {
select.query_list(QUERY, function(err, records) {
if (err) return next(err);
res.send({ results : records });
next();
});
});
In terms of using Sinon to test something like this: it really depends on the exact implementation. I can provide a quick example on how to stub the above usage of select.query_list, to make sure that the response contains the correct data.
The basic stub looks like this:
sinon.stub(select, 'query_list').yieldsAsync(null, [ { id : 'test' } ]);
What this does, is when select.query_list() gets call, it will call the first callback argument it receives (it does this by checking each argument to see which is a function) with the arguments null, [ { id : 'test' } ].
Those are the err and records arguments of the callback function passed in the handler. So you can use this to skip the database query entirely and pretend that the query yielded a particular array of records.
From there, res.send() gets called (which was the issue that you initially ran into: it didn't get called at all because it was being performed in a part of your app that wasn't getting called because of your stub) and you can check in your test if the resulting response data is as expected.
It becomes a bit more complicated if you want to stub a function deeper in the call stack, but with the correct Sinon tools (like .yields*, or using spies instead of stubs) it's usually not terribly difficult (provided that all the functions that you want to stub/spy are accessible, that is, exported).

Testing Express and Mongoose with Mocha

I'm trying to test my REST API endpoint handlers using Mocha and Chai, the application was built using Express and Mongoose. My handlers are mostly of the form:
var handler = function (req, res, next) {
// Process the request, prepare the variables
// Call a Mongoose function
Model.operation({'search': 'items'}, function(err, results) {
// Process the results, send call next(err) if necessary
// Return the object or objects
return res.send(results)
}
}
For example:
auth.getUser = function (req, res, next) {
// Find the requested user
User.findById(req.params.id, function (err, user) {
// If there is an error, cascade down
if (err) {
return next(err);
}
// If the user was not found, return 404
else if (!user) {
return res.status(404).send('The user could not be found');
}
// If the user was found
else {
// Remove the password
user = user.toObject();
delete user.password;
// If the user is not the authenticated user, remove the email
if (!(req.isAuthenticated() && (req.user.username === user.username))) {
delete user.email;
}
// Return the user
return res.send(user);
}
});
};
The problem with this is that the function returns as it calls the Mongoose method and test cases like this:
it('Should create a user', function () {
auth.createUser(request, response);
var data = JSON.parse(response._getData());
data.username.should.equal('some_user');
});
never pass as the function is returning before doing anything. Mongoose is mocked using Mockgoose and the request and response objects are mocked with Express-Mocks-HTTP.
While using superagent and other request libraries is fairly common, I would prefer to test the functions in isolation, instead of testing the whole framework.
Is there a way to make the test wait before evaluating the should statements without changing the code I'm testing to return promises?
You should use an asynchronous version of the test, by providing a function with a done argument to it.
For more details refer to http://mochajs.org/#asynchronous-code.
Since you don't want to modify your code, one way to do that could be by using setTimeout in the test to wait before to call done.
I would try something like this:
it('Should create a user', function (done) {
auth.createUser(request, response);
setTimeout(function(){
var data = JSON.parse(response._getData());
data.username.should.equal('some_user');
done();
}, 1000); // waiting one second to perform the test
});
(There might be better way)
Apparently, express-mocks-http was abandoned a while ago and the new code is under node-mocks-http. Using this new library it is possible to do what I was asking for using events. It's not documented but looking at the code you can figure it out.
When creating the response object you have to pass the EventEmitter object:
var EventEmitter = require('events').EventEmitter;
var response = NodeMocks.createResponse({eventEmitter: EventEmitter});
Then, on the test, you add a listener to the event 'end' or 'send' as both of them are triggered when the call to res.send. 'end' covers more than 'send', in case you have calls other than res.send (for example, res.status(404).end().
The test would look something like this:
it('Should return the user after creation', function (done) {
auth.createUser(request, response);
response.on('send', function () {
var data = response._getData();
data.username.should.equal('someone');
data.email.should.equal('asdf2#asdf.com');
done();
});
});

Resources