Error handling within an SPA can be challenging, most of the app uses some sort of backend service to store the app data, and things can go wrong due to network connection or a bad user Input. Working with Vue.js app I have figured that to handle all the API related errors best bet is to use axios inteceptor.
Typical error handling
Since axios
is promise based HTTP library we can handle any error using then(response)
and catch(error)
, but if you are not careful you will end up scattering catch()
block throughout your application. In most cases, you will be showing some type of alert message to the screen.
axios.get('/user/1').then((response) => {
console.log('Everything is awesome.');
}).catch((error) => {
console.warn('Not good man :(');
})
It’s good for a small app but as your apps start growing it will be messy to maintain it, and if you decided to change the way you show errors to a user you will be doing a lot of work to update in all places.
Interceptor Error handling
Here comes the clean way of handling errors with axios interceptors. Idea is to check the error response and define some set of rules to show the errors on the global level. With this you don’t need the catch(error) block where you make axios call to handle request errors, all the error will be handled by interceptor.
Add following in resources/assets/js/bootstrap.js
after axios import.
axios.interceptors.response.use(
function(response) { return response;},
function(error) {
// handle error
if (error.response) {
alert(error.response.data.message);
}
});
Error Notification
Just plain alert() box is not a great way to show errors, you cannot customize them and they look very ugly. I got this iziToast library which is pretty neat with lots of configuration and sweat animation backed in.
We can use it directly like this in our app but I prefer to create a wrapper around it so If some reason I need to change the notification system later I can just update the wrapper and everything will work with new hotter toast 2.0 notification in future 😎
Now pull the iziToast using npm and create a resources/assets/js/services/toast.js
and add following code.
import 'izitoast/dist/css/iziToast.min.css'
import iZtoast from 'izitoast'
const toast = {
error: (message, title = 'Error') => {
return iZtoast.error({
title: title,
message: message,
position: 'bottomCenter'
});
},
success: (message, title = 'Success') => {
return iZtoast.success({
title: title,
message: message,
position: 'bottomCenter'
});
}
};
export default toast;
For simplicity, I am keeping only error
and success
type notification, but you can add all the features your app needed for notification.
Now let’s update the interceptor to use our toast notification instead boring alert box.
import toast from './toast'
axios.interceptors.response.use(
...
function(error) {
// handle error
if (error.response) {
toast.error(error.response.data.message);
}
});
Turn off error handling
I came across many scenarios where a global error handler was not a good choice. For example, if there is a request which error format is different or unique like currently, we are expecting message
key in error response which is default in Laravel for any error. But if you are making calls to different API provider or you want to customize the notification, what to do then? Don’t worry axios configuration can help with this.
axios.get('/user/1', {errorHandle: false}).then((response) => {
console.log('Everything is awesome.');
}).catch((error) => {
// handle this error here
alert('Not good man :(');
})
By passing a config property {errorHandle: false}
in axios request we can turn off the error handling for this call. Now let’s modify the interceptor to make it happen.
axios.interceptors.response.use(
function (response) {
return response;
},
function(error) {
// check for errorHandle config
if( error.config.hasOwnProperty('errorHandle') && error.config.errorHandle === false ) {
return Promise.reject(error);
}
if (error.response) {
toast.error(error.response.data.message);
}
});
This makes it very flexible, now you can turn off global error handling for a specific endpoint call and you can use catch(error)
to handle the error.
Error handler module
Since all the app needs this error handling let’s put it in a module so you can reuse it in any vuejs app or any app in general which uses axios to make HTTP calls.
Create resources/assets/js/services/errorHandler.js
and add following code
import axios from 'axios'
import toast from './toast'
function errorResponseHandler(error) {
// check for errorHandle config
if( error.config.hasOwnProperty('errorHandle') && error.config.errorHandle === false ) {
return Promise.reject(error);
}
// if has response show the error
if (error.response) {
toast.error(error.response.data.message);
}
}
// apply interceptor on response
axios.interceptors.response.use(
response => response,
errorResponseHandler
);
export default errorResponseHandler;
This module is applying the response interceptor to handle the errors during a request. To use it just import it in your resources/assets/js/bootstrap.js
like this.
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
require('./services/errorHandler');
If you want to Display Form input error, do check out the vue directive which is based on a similar implementation.
I hope it gives you a clean way to handle the error in your API, let me know in the comments, see you next time.