I use HTTP requests to get data for my Vue.js application. I have one file called Api.js with the base axios instance:
export default () => {
return axios.create({
baseURL: apiURL,
headers: {
Authorization: `JWT ${store.state.token}`
}
})
}
than I have a file called service.js, which contains the functions for the different endpoints:
export default {
status() {
return Api().get('status/')
}
}
In the .vue file I call the method like that.
created() {
Service.status()
.then(response => {
// do something with the data
})
.catch(e => {
// show exception
})
}
Some exceptions should be handled in Api.js (for example: 401), some other exceptions should be handled in service.js and others in the .vue file. How can I do that?
Disclaimer: I have created two small axios plugins to achieve this specific pattern easily.
axios-middleware
Simple axios HTTP middleware service to simplify hooking to HTTP requests made through Axios.
It uses axios interceptors as mentioned by acdcjunior but it abstracts the use of axios with a commonly known middleware pattern so your app doesn't need to know and deal with the interceptor syntax.
// import your API's axios instance
import http from './api';
import { Service } from 'axios-middleware';
// Create a new service instance
const service = new Service(http);
// We're good to go!
export default service;
You can then use this middleware service to register different middlewares anywhere in your app. A middleware can be as simple as an object or a reusable, easily testable class.
import i18n from './services/i18n';
import toast from './services/toast';
import service from './services/middleware';
import { ApiErrorMiddleware, OtherMiddleware } from './middlewares';
// Then register your middleware instances.
service.register([
// Middleware class instance
new ApiErrorMiddleware(i18n, toast),
new OtherMiddleware(),
// or a simple object
{
onRequest() {
// handle the request
},
onResponseError(error) {
// handle the response error
}
}
]);
Where the ApiErrorMiddleware
would be a simple class with the sole responsibility of showing toast messages on error.
export default class ApiErrorMiddleware {
/**
* @param {VueI18n} i18n instance
* @param {Object} toast message service
*/
constructor(i18n, toast) {
this.toast = toast;
this.i18n = i18n;
}
/**
* @param {Object} error
*/
onResponseError(error = {}) {
const { response } = error;
let key = 'errors.default';
if (response && this.i18n.te(`errors.${response.status}`)) {
key = `errors.${response.status}`;
} else if (error.message === 'Network Error') {
key = 'errors.network-error';
} else {
// TODO log unhandled errors
}
this.toast.error(this.i18n.t(key));
}
}
axios-resource
Simple axios resource class to easily interact with a REST endpoint.
Define a resource class. Here, I added onError
and onFetchError
as examples for your use-case.
import Resource from 'axios-resource';
export default class UserResource extends Resource {
static URL = 'user/{id}';
// This calls `sync` in the background
fetch() {
return super.fetch.apply(this, arguments)
.catch(err => this.onFetchError(err));
}
onFetchError(err) {
// An error occurred while fetching this resource.
}
onError(err) {
// An error occurred with this resource
}
// called for every actions (fetch, create, patch, delete)
sync() {
return super.sync.apply(this, arguments)
.catch((err) => this.onError(err))
}
}
Then, in api.js
, create an instance.
import UserResource from './user';
const user = new UserResource();
// GET https://example.com/api/user/me
user.fetch('me')
.then(({ data }) => {
console.log('User data:', data);
});
The error can be dealt with at every step.
- in the
onFetchError
of this specific resource
- in the
onError
of this resource
- in a middleware for the app.
You should add axios interceptors:
Axios Interceptors
You can intercept requests or responses before they are handled by
then
or catch
.
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
Those can (should) be in your Api.js.