typescriptvue.jsaxiosvue-composition-api

How can I make Axios globally available in a Vue 3 application using the Composition API with TypeScript?


I've tried using provide/inject:

plugins/axios.ts:

import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { App } from 'vue'

export default {
    install: (app: App, baseUrl: string) => {
        const axiosInstance: AxiosInstance = axios.create({ baseURL: baseUrl })
        axiosInstance.defaults.headers.post['Content-Type'] = 'application/json'
        app.provide('axios', axiosInstance)
    }
}

plugins/index.ts

import router from '@/router'
import { createPinia } from 'pinia'
import { axios } from '@/plugins/axios'
import type { App } from 'vue'

export function registerPlugins(app: App) {
    const pinia = createPinia()
    app.use(pinia).use(router)
    axios.install(app, 'http://localhost:4000')
}

main.ts

import App from '@/App.vue'
import { createApp } from 'vue'
import { registerPlugins } from '@/plugins'

const app = createApp(App)
registerPlugins(app)
app.mount('#app')

Now, attempt to use it in stores/someStore.ts (this is psuedo-code):

import { inject } from 'vue'
import type { AxiosInstance } from 'axios'
import { defineStore } from 'pinia'

export const useSomeStore = defineStore('SomeStore', () => {
    const axios = inject<AxiosInstance>('axios')

    const getSomeWidgets = async () => {
        return await axios.get('/widgets')
    }

    return { getSomeWidgets }
})

This results in a warning when the store is loaded:

[Vue warn]: injection "axios" not found.

and an eventual "undefined" error when the store method is actually called.

If I attempt to use globalProperties in my axios.ts plugin, like this:

app.config.globalProperties.$axios = axiosInstance

I don't understand how I can access it in stores/components, because this is not available in the Composition API.

// Does not even compile because `this` is undefined
return await this.$axios.get('/widgets')

I've asked AI, Googled everything, paid for tutorials, and still can't find a definitive, simple answer to this question. Can someone please show me how I can make Axios available globally in the Vue 3 Composition API with TypeScript?

EDIT: Complete solution below

In the interest of helping Google find results that I could not, here is the solution I rested on after @Estus Flask's help:

Create a module that constructs the Axios object, put it wherever, I left it in my plugins folder:

import axios from 'axios'

const axiosInstance = axios.create({
    baseURL: `${import.meta.env.VITE_API_HOST}:${import.meta.env.VITE_API_PORT}`,
    headers: { 'Content-Type': 'application/json' }
})

// set up interceptors or whatever else you need on the Axios instance

export default axiosInstance

Then, create a composable for using Axios in components and/or stores. I chose to call mine "ApiStore" and put it in my stores folder. The API I'm calling is GraphQL, so I can actually do some helpful stuff in here:

import { defineStore } from 'pinia'
import axiosHttp from '@/plugins/axiosHttp'

export const useApiStore = defineStore('ApiStore', () => {
    const query = async (name: string, parameters: Record<string, any>, resultSelector: string) => {
        const response = await axiosHttp.post('', {
            query: `{
                ${name}(
                    ${Object.entries(parameters)
                        .map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
                        .join(', ')}) {
                            ${resultSelector}
                        }
                    }`
        })
        return response.data.data[name]
    }

    return { query }
})

I can then use this in my other stores like this:

import { defineStore } from 'pinia'
import { useApiStore } from '@/stores/apiStore'

export const useWidgetStore = defineStore('WidgetStore', () => {
    const apiStore = useApiStore()
    // state, getters, etc.

    async function getWidgets(categoryId) {
        return await apiStore.query('widgets', { category: categoryId }, `id, name, creationTime`)
    }

    return { getWidgets } 
})

Solution

  • The use of inject is limited to components because provide/inject relies on component hierarchy, while Pinia stores don't have this restriction, so it's incorrect to use it there.

    Unless there are some specific requirements to contain Axios instance within Vue app instance (some multi-app build, SSR setup, concurrently running automated tests, etc), there is no need to make it more complicated.

    It's as simple as creating a singleton in a module:

    export default axios.create(...)
    

    There's nothing wrong with importing it directly through the app where it's used. But it can be wrapped with a composable to use in components for more flexibility:

    import myAxios from '@/my-axios';
    
    export default function useMyAxios() {
      return myAxios;
    }
    

    The composable is of no practical importance at the moment, but will save from refactoring in case the mentioned specific requirements appear.