Error Handling in Sails.js - sails.js

I am just starting using Sails.js and it's an amazing framework. But I've met some situation and I cannot find solution by Google so I came here for help.
I have a controller to connect to another remote service with very old-designed API full of XML response and inconsistency, wrapping that service in simple and clean APIs. So I have some routers like:
list: function(req, res) {
params = {
...
}
FooService.request(data, function(error, response) {
res.send(response)
})
process.once('uncaughtException', function(err) {
res.send(500, '[Foo] ' + err);
});
},
The 'process.once' is for async exceptions which may raised in the FooService.request process. I know this is bad code and my question is: how to handle such situation more Sails.js way?
In Node.js we have Domain and connect-domain, which are designed for such problems. Because Sails.js is basically Express, which can facilitate connect-domain very well, I think there may be some idiomatic way to do that.
I've tried adding this in config/local.js:
module.exports = {
...
express: {
customMiddleware: function(app) {
console.log('Custom middleware config called')
var domain = require('connect-domain')
app.use(domain())
.use(function(err, req, res, next) {
console.log('Error catched!')
res.send(500, '[Foo] ' + err)
})
}
}
};
When un-catched exception occurred, it will not crash server and error 500 being returned to client side ('app.use(domain())' works). But the custom error handler does not called. Have no idea why.

If you're in control of the FooService code, then the best option is to handle all errors that happen there by calling the callback for FooService.request early with the error, and then using res.serverError or some other response in your controller:
FooService.request(data, function(error, response) {
if (error) {return res.serverError(errror);}
res.send(response)
});
If the FooService is using packages that you don't control, which may themselves throw errors inside of async code that they aren't catching (bad code!) then another good option is to use Node's error domains. See this answer for an example of someone doing a quick wrapper to use domains to catch errors in asynchronous code.

Related

Using .catch with async/await

I am wondering if I can still use .catch() within an async function to catch the error instead of using a try-catch block.
The following code is from my project using MongoDB and Express:
router.get('/communities', EnsureAuthenticated, async (req, res) =>{
//Look up the user in the db + populate the community field
const userInfo = await User_DB
.findOne({ _id:req.user._id, })
.populate('communities')
.lean()
.catch(err => {
console.log(err)
res.status(500)
.json({
msg: 'DB: Error Fetching User Info',
});
// rest of the functions that take userInfo as the input
});
When using try-catch all variables will be limited to within the scope of that try-catch block.
If I need to use the userInfo as the input for other functions down the line I'll have to put everything within that try-catch block which doesn't look clean and can be confusing. Because you don't know which function does the error belongs to if there is any.
Is my understanding correct?
I apologize for the formatting. I'm doing this on my phone.
You can also make a central error handler, you can find an example here

Sails.js: error logging in with passport

When this part of my code gets executed:
req.login(user, function (err){
if (err) return res.negotiate(err);
return res.redirect('/welcome');
});
I get the following error:
/home/oriol/Desktop/containers/node_modules/sails-mongo/node_modules/mongodb/lib/utils.js:98
process.nextTick(function() { throw err; });
^
Error: passport.initialize() middleware not in use
at IncomingMessage.req.login.req.logIn (/home/oriol/Desktop/containers/node_modules/passport-local/node_modules/passport/lib/passport/http/request.js:30:30)
at /home/oriol/Desktop/containers/api/controllers/AuthController.js:37:15
at wrapper (/home/oriol/Desktop/containers/node_modules/lodash/index.js:3592:19)
at applyInOriginalCtx (/home/oriol/Desktop/containers/node_modules/waterline/lib/waterline/utils/normalize.js:421:80)
at wrappedCallback (/home/oriol/Desktop/containers/node_modules/waterline/lib/waterline/utils/normalize.js:324:18)
at success (/home/oriol/Desktop/containers/node_modules/waterline/node_modules/switchback/lib/normalize.js:33:31)
at _switch (/home/oriol/Desktop/containers/node_modules/waterline/node_modules/switchback/lib/factory.js:58:28)
at /home/oriol/Desktop/containers/node_modules/waterline/lib/waterline/query/dql/create.js:248:9
at /home/oriol/Desktop/containers/node_modules/async/lib/async.js:52:16
at /home/oriol/Desktop/containers/node_modules/async/lib/async.js:269:32
at /home/oriol/Desktop/containers/node_modules/async/lib/async.js:44:16
at child.<anonymous> (/home/oriol/Desktop/containers/node_modules/waterline/lib/waterline/utils/schema.js:152:44)
at fn (/home/oriol/Desktop/containers/node_modules/waterline/lib/waterline/utils/callbacksRunner.js:60:10)
at /home/oriol/Desktop/containers/node_modules/async/lib/async.js:181:20
at iterate (/home/oriol/Desktop/containers/node_modules/async/lib/async.js:262:13)
at Object.async.forEachOfSeries.async.eachOfSeries (/home/oriol/Desktop/containers/node_modules/async/lib/async.js:281:9)
at Object.async.forEachSeries.async.eachSeries (/home/oriol/Desktop/containers/node_modules/async/lib/async.js:214:22)
at Object.runner.afterCreate (/home/oriol/Desktop/containers/node_modules/waterline/lib/waterline/utils/callbacksRunner.js:63:9)
at after (/home/oriol/Desktop/containers/node_modules/waterline/lib/waterline/query/dql/create.js:243:17)
at /home/oriol/Desktop/containers/node_modules/waterline/lib/waterline/query/dql/create.js:230:68
at wrapper (/home/oriol/Desktop/containers/node_modules/lodash/index.js:3592:19)
at applyInOriginalCtx (/home/oriol/Desktop/containers/node_modules/waterline/lib/waterline/utils/normalize.js:421:80)
I have read other posts about the same issue but none of them seems to fix it. I don't know much about why is this happening, feel free to ask for further explanation or any piece of code that might be relevant. Thanks!
I just found out now how to solve that. This question can be closed.
I did this for the second time and somehow it worked: Sails.js + Passport.js: passport.initialize() middleware not in use.

Send JSON and redirect with Passport authenticate

I'm working with ExpressJS and Passport to authenticate local users, and ideally I want to reply with both a JSON object and a redirect upon success or failure. My code right now looks like this:
app.post('/login', function(req, res, next) {
passport.authenticate('local-signin', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
res.json(false);
return res.redirect('/signin');
}
req.logIn(user, function(err) {
if (err) {
return next(err);
}
res.json(user);
return res.redirect('/');
});
})
(req, res, next);
});
The problem is that res.json() and res.redirect() both cause the response to become Finished. Is there some way to have the two happen together? The reason I want to send JSON as well as a redirect is that the login also needs to work from a mobile app, for which redirect doesn't seem to do anything.
If there are better ways to accomplish that, that'd be great too, but the additional info I want to send as JSON would be helpful on the website side too.
Thanks!
EDIT: It looks like I can work with 2 separate routes, however, I'd still like to know if it's possible to both send JSON and redirect.

Error handling with Mongoose

I am an absolute NodeJS beginner and want to create a simple REST-Webservice with Express and Mongoose.
Whats the best practice to handle errors of Mongoose in one central place?
When anywhere an database error occurs I want to return a Http-500-Error-Page with an error message:
if(error) {
res.writeHead(500, {'Content-Type': 'application/json'});
res.write('{error: "' + error + '"}');
res.end();
}
In the old tutorial http://blog-next-stage.learnboost.com/mongoose/ I read about an global error listener:
Mongoose.addListener('error',function(errObj,scope_of_error));
But this doesn't seem to work and I cannot find something in the official Mongoose documentation about this listener. Have I check for errors after every Mongo request?
If you're using Express, errors are typically handled either directly in your route or within an api built on top of mongoose, forwarding the error along to next.
app.get('/tickets', function (req, res, next) {
PlaneTickets.find({}, function (err, tickets) {
if (err) return next(err);
// or if no tickets are found maybe
if (0 === tickets.length) return next(new NotFoundError));
...
})
})
The NotFoundError could be sniffed in your error handler middleware to provide customized messaging.
Some abstraction is possible but you'll still require access to the next method in order to pass the error down the route chain.
PlaneTickets.search(term, next, function (tickets) {
// i don't like this b/c it hides whats going on and changes the (err, result) callback convention of node
})
As for centrally handling mongoose errors, theres not really one place to handle em all. Errors can be handled at several different levels:
connection errors are emitted on the connection your models are using, so
mongoose.connect(..);
mongoose.connection.on('error', handler);
// or if using separate connections
var conn = mongoose.createConnection(..);
conn.on('error', handler);
For typical queries/updates/removes the error is passed to your callback.
PlaneTickets.find({..}, function (err, tickets) {
if (err) ...
If you don't pass a callback the error is emitted on the Model if you are listening for it:
PlaneTickets.on('error', handler); // note the loss of access to the `next` method from the request!
ticket.save(); // no callback passed
If you do not pass a callback and are not listening to errors at the model level they will be emitted on the models connection.
The key take-away here is that you want access to next somehow to pass the error along.
hey this is the simplest way i found..
try { } catch (error) {
console.log(error);
// checking validation
if (error.name === "ValidationError") {
const message = Object.values(error.errors).map(value => value.message);
return res.status(400).json({
error: message
})
}
res.status(400).json(error.message)
}
}
just copy paste

How to keep DRY when using node-mongodb-native

db.open(function(err,db){
//handle error
db.collection("book",function(err, collection){
//handle error
collection.doSomething1(... function(err, result){
//handle error
collection.doSomething2(... function(err, result){
...
})
})
})
})
but we wont wrote db.open every time when we want do something, but we must make sure that db has opened when we use it.
we still wont like handle error every time in the same code.
we can also reuse the collection.
just like this
errorHandledDB.doSomething1("book",... function(result){
errorHandledDB.doSomething2("book",...function(result){
...
})
})
I implemented a server-application using mongodb for logging. I implemented data access using some provider classes, as shown in the example.
provider.filelog.js
var Db= require('mongodb/db').Db,
ObjectID= require('mongodb/bson/bson').ObjectID,
Server= require('mongodb/connection').Server,
log = require('lib/common').log;
FilelogProvider = function (host, port, database) {
this.db= new Db(database, new Server(host, port, {auto_reconnect: true}, {}));
this.db.open(function(){});
};
FilelogProvider.prototype.getCollection= function(callback) {
this.db.collection('filelogs', function(error, log_collection) {
if (error) callback(error);
else {
log_collection.ensureIndex([[ 'created', 1 ]], false, function(err, indexName) {
if (error) callback(error);
callback(null, log_collection);
});
}
});
};
FilelogProvider.prototype.findAll = function(callback) {
this.getCollection(function(error, log_collection) {
if (error) callback(error);
else {
log_collection.find(function(error, cursor) {
if (error) callback(error);
else {
cursor.toArray(function(error, results) {
if (error) callback(error);
else callback(null, results);
});
}
});
}
});
};
Since i use Grasshopper as my http-middleware, i can easily inject the providers using the DI functionality provided by gh:
server.js
gh.addToContext({
providers: {
filelog: new FilelogProvider(conf.mongodb_host, conf.mongodb_port, conf.mongodb_database),
status: new ServerstatusProvider(conf.mongodb_host, conf.mongodb_port, conf.mongodb_database)
},
log: log
});
Accessing the providers in every controller function is now a breeze:
gh.get('/serve', function() {
this.providers.filelog.findAll(function(err, res) {
// access data here
});
});
This implementation is pretty specific to Grasshopper (as it's using DI) but i think you'll get the idea. I also implemented a solution using express and mongoose, you find it here. This solution is a bit cleaner than using the native driver, as it exposes models to use against the database.
Update
Just for the sake of it: if you really want to stick to the DRY-principle, stop tinkering on an ORM implementation yourself and use Mongoose. If you need special functionality like Map/Reduce, you still can use the native driver (on which Mongoose is built).
Answer my own question. Because there is no more good options, I do it myself, I start a project to simplify it, check node-mongoskin.
I'm talking theoretically here, with no regards to mongo.
I would recommend you to try building a wrapping of a kind.
A Data access layer or at least models, it all depends on your architecture and needs,
and that's on your side.
Just wrap the access to mongodb with a layer of abstract commands, than write an abstract model object and all other model objects will inherit from it, and will automatically set all getters and setters for the attributes of the record you pulled from the mongo db.
for updating you just give it a save method, that iterates and saves all the changes made to it.
Since it's not a relational and I don't know if this is well suited for your design, the model may not be useful here.
Hope this helps, Good luck!