{
"errors": {
"email": "Invalid e-mail address"
}
}
Proper RESTful API’s use status codes to indicate the result of requests. This means that for PUT
/PATCH
requests a 400 Bad Request
or a 409 Conflict
will be returned if the data in the request is invalid. Since this
is a solvable error for the end user, the API might return descriptive errors, perhaps something like this:
{
"errors": {
"email": "Invalid e-mail address"
}
}
Awesome, but if you want to actually show these errors to the end user you’ll need access to
the request data. If you’re using jQuery, accessing data for requests with a 2xx (success) status code is easy;
if the request’s Content-Type
is application/json
, jQuery will parse it for you. For 4xx and 5xx requests not
so much though; you need to do something like this:
$.post('/whatever', '{}').fail(function(xhr) {
try {
var validationErrors = JSON.parse(xhr.responseText);
displayErrors(validationErrors.errors);
} catch(e) {
// Invalid JSON error handling
}
});
If you’re a bit lazy like me though, you don’t want to do this manually for every scenario in which you’ll be handling
errors like this. Preferably, the JSON data is automatically available in every XHR’s fail callback.
Unfortunately, $.ajaxError
and $.ajaxComplete
are worthless in this scenario, since they fire after user defined
callbacks. The solution is overriding $.ajax
so you can always assign the first callback. A solution like this
can be found on this page, but it is
incomplete, as any "error" callback assigned through in `$.ajax’s options will still run before the global callback.
I therefore rewrote this code to work in that scenario as well:
(function($) {
var old = $.ajax;
$.ajax = function(url, options) {
if (!options) {
options = url || {};
}
var fail;
if (options.error) {
// Remove the error callback and add it
// as a fail callback on the XHR later on.
fail = options.error;
delete options.error;
}
var xhr = old.call(this, url, options).fail(function(xhr) {
if (xhr.getResponseHeader('content-type').indexOf('application/json') > -1) {
try {
xhr.JSONdata = JSON.parse(xhr.responseText);
} catch(e) {}
}
});
if (fail) {
// Assign the error callback
xhr.fail(fail);
}
return xhr;
}
})(jQuery);
There you go! Now in your fail callback you can simply use:
$.post('/whatever', '{}').fail(function(xhr) {
if (xhr.JSONdata) {
displayErrors(xhr.JSONdata.errors);
}
});