4
votes

Until now, I had been importing axios in each Vue component where I wanted to make HTTP requests, like this.

<script lang="ts">
import axios from 'axios';

@Component
export default class ExamplePage extends Vue {
  created(): void {
    axios.post(some_path);
  }
}

However, now I want to define a global interceptor for all axios requests, basically to catch all 401 unauthorized responses from the backend server (Rails) and log out the user. My understanding so far is that you must instantiate axios once and use it everywhere, instead of importing and using a different instance in each file.

I've referred to this and this, and tried the below.

// application.js

import '../assets/sass/base.sass';
import App from '../app';
import Vue from 'vue';
import VueRouter from 'vue-router';
import axios from 'axios';
import router from '../routes';

Vue.use(VueRouter);

document.addEventListener('DOMContentLoaded', () => {
  new Vue({
    el: '#application',
    router,
    render: (h) => h(App),
  });
});

axios.interceptors.response.use(
  response => response,
  error => {
    const status = error.response;
    if(status === 401) {
      // do stuff
    }
  }
);

Vue.prototype.$http = axios

When I tried to call this.$http.put(...) in another file, it said property $http doesn't exist (I'm guessing it's because this in the context of that component is the component itself, but I'm not sure). How can I fix this?

[UPDATE] Thanks to the responses, I decided to initialize an axios instance in a separate file and then use that instead. However, this is still not working. The 401 responses don't seem to be triggering the interceptor at all.

Is there some additional configuration necessary?

// axios-instance.ts
import axios, { AxiosInstance } from 'axios';

const axiosInstance: AxiosInstance = axios.create();

axiosInstance.interceptors.response.use(
  response => response.data,
  async function(error) {
    console.log("THIS IS THE 401 INTERCEPTOR")
    const status = error.response;
    if(status === 401) {
      // Log out user
    }
  }
);

export default axiosInstance;

// Some component
<script lang="ts">
import axiosInstance from 'axios-instance';

@Component
export default class ExamplePage extends Vue {
  created(): void {
    axiosInstance.post(some_path);
  }
}
5
You can't write this.$http.interceptors in application.js, you'd need to use axios.interceptors. If that isn't the cause of your problem could you please update your question to include the call to put that isn't working. Without some context it's difficult to tell whether you're doing that correctly or not.skirtle
@skirtle I changed my reference to the axios instance to axios, but when I try to call it in any other files it still goes "Property '$http' does not exist on type 'ExamplePage' (name of component)". As I wrote above, it's probably because this in the context of that component refers to the component itself. Further advice appreciated.reesaspieces
Since I last looked it seems the question has significantly changed and is now in TypeScript. Difficult to give any kind of answer when the question changes so much. In your latest code you have const status = error.response;, which should probably be const status = error.response.status;. The error handler also isn't returning a rejected promise, which may prove problematic.skirtle

5 Answers

8
votes

Doesn't solve your question directly, but the way I create instance of axios is:

// axios-instance.js

import axios from 'axios'

const instance = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});

export default instance

After you just import the instance

// application.js

import axios from '../../axios-instance'
3
votes

You can't use this when you aren't in the vue instance. Try this:

// application.js

import '../assets/sass/base.sass';
import App from '../app';
import Vue from 'vue';
import VueRouter from 'vue-router';
import axios from 'axios';
import router from '../routes';

Vue.use(VueRouter);

axios.interceptors.response.use(
  response => response,
  error => {
    const status = error.response;
    if(status === 401) {
      // do stuff
      }
    }
  }
);

Vue.prototype.$http = axios;

const VueInstance = new Vue({
    el: '#application',
    router,
    render: (h) => h(App),
});

Now you can make HTTP request using this.$http:

<script>
    export default {
        methods: {
            doStuff() {
                this.$http.post('path_to_post_request').then(({ data }) => {
                    // do something with received data
                }).catch((e) => {});
            }
        }
    }
</script>
1
votes

I had the same problem. It is not so clear in Axios API docs that error responses should be intercepted in the response interceptor's error callback.

It should be done like this:

axios.interceptors.response.use(function (response) {
  //This is the success callback in case u ever need it
  return response;
}, function (error) {
  // This is the callback I was talking about.
  // Do something with request error
  // Tip: error.response is the actual response with error code
  return Promise.reject(error);
})

This can be seen in the Axios API Documentation - Interceptors section

1
votes

I recommend that you implement the axios instance in a separate js file:

// utils/request.js

import axios from 'axios'

const service = axios.create({
  baseURL: '',
  headers: {},
  withCredentials: true,
  // ...other options
})

// request interceptor
service.interceptors.request.use(config => {
  // ...
  return config
}, err => {
  return Promise.reject(err)
})

// response interceptor
service.interceptors.response.use(response => {
  // ...
  return response
}, err => {
  return Promise.reject(err)
})

export default service

And then you can using http request like this:

import request from '@/utils/request'

request({
  url: '',
  method: 'post',
  data: {}
}).then(res => {
  // Do something after the request is successful
}).catch(err => {
  // Do something after the request fails
})
1
votes

Try implementing a service:

src / services / yourservice.js

import axios from 'axios'

const apiClient = axios.create({
    baseURL: 'https://example.com/api',
    withCredentials: false,
    timeout: 1000,
   
    headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        'Authorization': 'Basic ' + btoa('api_username_here' + ':' + 'api_password_here')     
    }
})

export default {
    getOrders(){
        return apiClient.get('/orders?status=processing')
    },
    getProducts(id){
        return apiClient.get('/products/' + id)
    },
}

Use the service in a Component: src / component / yourcomponent.vue

import YourService from '@/services/YourService.js';
data() {
    return {
        orders: null, //Typescript
    }
},
created() {
    yourservice.getOrder(id).then(response => {
        this.orders = response.data 
    }).catch(error => {
        console.log(error) 
    });
}