Handle errors like a boss

Everyone knows that gracefully handling errors in your application is important. But sometimes, especially when you’re developing at the startup pace, it’s easy to put it aside for later while you work on… well, getting things to work.

I finally got a chance to start refactoring an application I built a few months ago. Among many sweet-baby-Jesus-did-I-actually-write-that-awful-code? moments, I noticed that my error handling was generally pretty weak — a popup here or console.log there. Now with a couple more months of experience under my belt, and a much stronger understanding of OOP, I knew I could better.

Step One: Outline The Requirements

First, I wanted to identify common elements in error handling so that I could keep my code as DRY as possible. Here’s the list I came up with:

  1. I want to call methods on an Error factory such that all errors can be handled in one line in my controllers (i.e. all the business logic is handled by the factory).
  2. I want to display a message to the user that an error has occurred when appropriate.
  3. Conversely, I do not want to display server errors that might reveal sensitive information or are just not appropriate for the user to see.
  4. I want to log all errors with enough detail to be able to quickly find, diagnose and fix the problems when they occur.
  5. I want error handling to function offline, so that errors can be logged even if the server cannot be reached for reporting.

Step Two: Design The Factory

Following these guidelines I created a relatively simple Error factory with 4 methods: public, private, message, and report. To accomplish goal #4, I pass the error that occurred along with its location in code. I can also pass additional notes like ‘admin/user/user.controller — calling the fetchAll function’ as needed. For goal #5, I save all the errors in an array in localStorage so that they can be reported in aggregate the next time the client connects to the server.

angular.module('app').factory('Error', function($http, $window){  var Error = {};  Error.public = function(error, location){
swal('Oops',
'Something went wrong.\n If the problem persists, please contact the system adminstrator.',
'error');
if ($window && $window.localStorage){
var errors = JSON.parse($window.localStorage.getItem('errors')) || [];
errors.push({
timestamp: new Date().toLocaleString(),
error: error,
location: location
})
$window.localStorage.setItem('errors', JSON.stringify(errors));
}
} return Error;})

The public method is for reporting an error to the user with a canned message. This would be appropriate for 500 errors, database errors, and other server-side problems where the nature of the error is not appropriate for the user to know, but an error message should be displayed nonetheless. I’m using the sweetalert plugin (swal) by the way — ngMessages, Toastr, or whatever messaging you like could be used.

The private method is exactly the same, except without the alert. I use for non-essential server calls, such as pre-fetching data so that it’s ready once the client navigates to that page.

Error.message = function(error, location, log){
swal('Oops', error, 'error');
if ($window && $window.localStorage && log){
var errors = JSON.parse($window.localStorage.getItem('errors')) || [];
errors.push({
timestamp: new Date().toLocaleString(),
error: error,
location: location
})
$window.localStorage.setItem('errors', JSON.stringify(errors));
}
}

The message method is similar, but it allows for the actual error message to be displayed. I use this one for things like validation errors where a resource name already exists. Additionally, I pass a ‘log’ flag so that I can quickly and easily specify whether or not it’s appropriate to log the exception for that particular function call.

Error.report = function(){
if ($window && $window.localStorage && $window.localStorage.getItem('errors')){
var errors = JSON.parse($window.localStorage.getItem('errors'));
$http.post('/api/errors', {errors: errors}).success(function(){
$window.localStorage.removeItem('errors')
}).error(function(err){
errors.push({
timestamp: new Date().toLocaleString(),
error: err,
location: 'Error.report'
})
$window.localStorage.setItem('errors', JSON.stringify(errors));
})
}
}

Finally, the report method sends all aggregated errors to an error handling endpoint on the server. I call this method once on initialization of the factory (which will log another error if the client still cannot connect).

Step Three: Server-side Reporting

The last step in the process was deciding how I wanted to be notified of these errors. I decided that I’d like to get a log emailed to me whenever the error endpoint was hit so that I could track down problems quickly. That’s pretty easy with mailgun and express.

router.post('/', function(req, res){
var data = {
from: 'App Error Log <admin@mysite.com>',
to: 'patrick@mysite.com',
subject: 'App: Error Log',
text: `Error report for app:
${req.body.errors.reduce(function(str, item, idx){
return `${str}\n\n${idx+1})\nTime: ${item.timestamp}\nError: ${item.error}\nLocation: ${item.location}`
}, '')}
`
};
mailgun.messages().send(data, function (error, body) {
if (err) return handleError(error, res);
return res.status(200).send('thanks');
});
});

I’m sure there are nicer ways to format it, but that works for the first iteration.

With everything in place, I now had a robust error handling system that I could drop into all my catch statements like this:

Error.message(err, 'admin/challenges/detail - add new tag', true)

Conclusion

I found creating this factory really exciting. I’ve been working with some more complex software architecture over the past few months, and I definitely feel like I’m gaining a lot of insight into the power and flexibility of OOP. Now I’m starting to look at all of my functions to see how I can optimize them to handle multiple configurations. In fact, I’ll probably go back and refactor this factory to take a configuration object rather than a list of arguments. I think it will improve readability and scalability.

Feel free to let me know what you think in the comments!

Patrick Shaughnessy

Written by

Full Stack Javascript Developer. Student of life.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade