Um plugin e composable para Vue 3 que simplifica chamadas de API com Axios, incluindo interceptors e um hook reativo similar ao useFetch do Nuxt
npm install v-api-fetchv-api-fetch?v-api-fetch não é apenas um composable, é a sua camada de dados completa e robusta para aplicações Vue 3. Inspirado nas melhores práticas do ecossistema, como o useFetch do Nuxt.js, ele oferece uma solução "pronta para produção" para simplificar, organizar e padronizar toda a comunicação com APIs no seu projeto.
Ele foi desenhado para ser configurado uma única vez e então utilizado de forma intuitiva em todos os seus componentes, cuidando de todo o trabalho pesado de gerenciamento de estado, reatividade e execução de requisições.
* 🔌 Configuração Centralizada (AxiosPlugin): Configure sua URL base, interceptadores de token e de erros em um único lugar (main.ts), e esqueça a configuração repetitiva.
* 🏭 Suporte a Múltiplas APIs (createApiInstance): Não se limite a uma única API. Crie, nomeie e configure quantas instâncias do Axios você precisar e acesse-as de forma simples em todo o seu projeto.
* ✨ Busca de Dados Declarativa (useApiFetch): Para buscar e exibir dados na tela. Ele gerencia automaticamente os estados de carregamento (pending), erros (error) e os dados (data) de forma reativa, com recursos avançados como retries, transform, initialData e um poderoso sistema de watch.
* ⚡ Ações Imperativas Simplificadas (useApi): Para quando você precisa executar ações como POST, DELETE, ou PUT. Tenha acesso direto à instância configurada do Axios para ter controle total sobre suas requisições.
* 🛡️ Totalmente Tipado: Construído com TypeScript para garantir segurança, autocomplete e uma excelente experiência de desenvolvimento.
---
O fluxo de trabalho com v-api-fetch é simples e lógico:
1. Você configura sua(s) API(s) uma única vez no main.ts usando o AxiosPlugin e, se necessário, createApiInstance.
2. Nos componentes, você busca dados para exibição usando useApiFetch.
3. Quando o usuário realiza uma ação (ex: envia um formulário), você executa a ação usando useApi.
4. Após a ação, você atualiza os dados na tela chamando a função execute retornada pelo useApiFetch.
``bash`
npm i v-api-fetch
O coração da configuração acontece no seu arquivo de entrada, main.js. É aqui que você informa ao plugin qual é o endereço da sua API e como ele deve se comportar antes de cada requisição (adicionar tokens) e ao encontrar erros.
1. Importe o AxiosPlugin do pacote:
`jsx`
import { AxiosPlugin } from 'v-api-fetch';
1. Use o plugin na sua instância do Vue (app) com app.use():
`jsx
app.use(AxiosPlugin, {
// 1. Configurações da URL da API (lidas do ambiente)
hostApi: import.meta.env.VITE_BASE_URL_API || 'http://localhost:8022',
subpath: import.meta.env.VITE_SUB_PATH || '',
baseApi: import.meta.env.VITE_BASE_API || '/api/v1',
// 2. Interceptor de Requisição: Adiciona o token de autorização
requestInterceptor: (config) => {
// Exemplo, pegando o token do keycloak (auth) e passando caso exista.
const keycloakStore = useKeycloakStore();
if (keycloakStore.token) {
// Adiciona o cabeçalho 'Authorization' em todas as chamadas
config.headers["Authorization"] = Bearer ${keycloakStore.token};
}
return config;
},
// 3. Interceptor de Erro: Lida com falhas na API (Opcional)
// função que pode ser adicionada para interceptar erros e fazer algo com eles
errorInterceptor: createErrorInterceptor(),
});
`
exemplo de errorInterceptor:
`jsx
import { useToastStoreLimited } from "@/stores/useToastStoreLimited";
import { useToastStore } from "@/stores/useToastStore";
function handleError(error) {
let finalMessage = "Ocorreu um erro inesperado.";
let errorTitle = "Erro";
if (error.request && !error.response) {
errorTitle = "Erro de Conexão";
finalMessage = "Não foi possível conectar ao servidor. A aplicação pode estar offline ou sua rede está com problemas.";
} else if (error.response) {
const status = error.response.status;
errorTitle = Erro ${status};
const errorData = error.response.data;
if (errorData && 'message' in errorData) {
if (typeof errorData.message === 'string') {
finalMessage = errorData.message;
} else if (Array.isArray(errorData.message)) {
finalMessage = errorData.message.map(m =>
typeof m === 'object' ? Object.values(m).join(', ') : m
).join('\n');
} else if (typeof errorData.message === 'object' && errorData.message !== null) {
finalMessage = Object.entries(errorData.message)
.map(([key, value]) => ${key}: ${Array.isArray(value) ? value.join(', ') : value})
.join('\n');
}
}
}
return { finalMessage, errorTitle };
}
function chooseToastStore(toastLimited) {
if (toastLimited === true) {
return useToastStoreLimited();
} else {
return useToastStore();
}
}
// Esta é a função que será passada para o plugin no main.js
export function createErrorInterceptor() {
return (error) => {
if (error.config?.showToast === false) {
return Promise.reject(error);
}
const path = window.location.pathname;
if (!path.startsWith("/panel")) {
return Promise.reject(error);
}
const { finalMessage, errorTitle } = handleError(error);
const toastStore = chooseToastStore(error.config?.toastLimited);
toastStore.showToast(errorTitle, finalMessage, 2);
return Promise.reject(error);
};
}
`
- hostApi, subpath, baseApi: Estas opções constroem a URL base para todas as suas chamadas. Usar variáveis de ambiente (import.meta.env) é uma excelente prática, pois permite configurar diferentes URLs para ambientes de desenvolvimento e produção.requestInterceptor
- : Esta função é executada antes de cada requisição ser enviada, no exemplo mostrado, ela busca o token de autenticação no useKeycloakStore e o anexa aos cabeçalhos. Isso centraliza a lógica de autenticação, garantindo que todas as chamadas de API sejam autenticadas.errorInterceptor
- : Esta função é executada sempre que uma chamada de API falha. Ao delegar a lógica para uma função externa (createErrorInterceptor), você mantém seu main.ts mais limpo e organizado. Essa função é responsável por formatar a mensagem de erro e exibir uma notificação (toast) para o usuário ou qualquer nova lógica que queira adicionar.
Para utilizar, primeiro importe o composable no seu componente Vue.
`jsx`
import { useApiFetch } from 'v-api-fetch';
A forma mais simples de usar o useApiFetch é passando apenas o path da Url do endpoint. Ele automaticamente fará a requisição e retornará o estado da chamada.
`jsx
Carregando notícias...
Ocorreu um erro: {{ error.message }}
`
O composable retorna um objeto com as seguintes propriedades reativas:
| Propriedade | Tipo | Descrição |
| --- | --- | --- |
| data | Ref | O corpo da resposta da API (ex: response.data). O valor inicial é null. |pending
| | Ref | Um booleano que indica se a requisição está em andamento. O valor inicial é true. |error
| | Ref | Contém o objeto de erro caso a requisição falhe. O valor inicial é null. |execute
| | Function | Uma função async para re-executar a chamada à API manualmente. |
> 💡 Dica: Se você usar múltiplos useApiFetch no mesmo componente, renomeie as variáveis para evitar conflitos:
>
`jsx`
const { data: data_1 , pending: pending_1, error: error_1 } = useApiFetch('newsAll/')
const { data: data_2 , pending: pending_2, error: error_2 } = useApiFetch('newsAll/')
Você pode passar um segundo argumento para o useApiFetch com um objeto de opções para customizar o comportamento da chamada.
| Opção | Tipo | Descrição |
| --- | --- | --- |
| params | Object | Function | Computed | Um objeto de parâmetros a ser enviado na URL da requisição (query string). Pode ser reativo. (já é reativo por padrão) |transform
| | Function | Uma função para modificar os dados brutos da API data. |onResponse
| | Function | Um "hook" executado quando a chamada é bem-sucedida. Consegue acessar: response, request e options. |onResponseError
| | Function | Um "hook" executado quando a chamada falha. Consegue acessar: error, request e options. |watch
| | Array | Um array de fontes reativas para observar. Qualquer mudança em uma dessas fontes irá disparar um refetch. |paramsReactives
| | Boolean | (Padrão: true) Um interruptor para desativar a reatividade automática do objeto params. |apiOptions
| | AxiosRequestConfig | tudo que for mandado para apiOptions será repassado para a requisição AXIOS, ex: caso queria passar algum cabeçalho para a requisição |initialData
| | any | Valor default para o data, ex: começar com um vetor vazio até que uma resposta seja retornada |
Use para limpar, formatar ou extrair apenas a parte dos dados que lhe interessa.
`jsxuserList
// O endpoint retorna { count: 2, results: [...] }
// Queremos que seja apenas o array results, mas com dados formatados.
const { data: userList } = useApiFetch('/users', {
transform: (apiResponse) => {
// Retorna apenas o array 'results', transformando cada item
return apiResponse.results.map(user => ({
id: user.id,
fullName: ${user.first_name} ${user.last_name},`
statusText: user.status_code === 1 ? 'Ativo' : 'Inativo',
registeredDate: new Date(user.created_at).toLocaleDateString('pt-BR')
}));
}
});
`jsx`
const { data: productList, pending } = useApiFetch('/products', {
initialData: []
});
Use para executar "efeitos colaterais" após a chamada, como atualizar outras refs ou stores.
`jsx
const proximo = ref(null);
const anterior = ref(null);
const { data: newsItem } = useApiFetch(news/${route.params.id}/, {`
onResponse: ({ response }) => {
// Atualiza outras refs com dados de paginação que vêm na resposta
proximo.value = response.data.proximo;
anterior.value = response.data.anterior;
},
onResponseError: ({ error }) => {
console.error("Erro ao carregar notícia:", error);
// Limpa as refs em caso de erro
proximo.value = null;
anterior.value = null;
}
});
Existem 3 formas de fazer o useApiFetch ser reativo e refazer a chamada quando um dado muda.
Se a própria URL for reativa (uma ref ou computed), o fetch será refeito sempre que ela mudar.
`jsx
import { computed } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
const newsId = computed(() => news/${route.params.id}/);
const { data: newsItem } = useApiFetch(newsId);
`
Este é o comportamento padrão. Se você passar um ref, computed ou reactive para a opção params, qualquer mudança nele irá disparar um refetch.
`jsx
import { computed } from 'vue';
const pagination = reactive({ search: '', current_page: 1 });
const apiParams = computed(() => ({
search: pagination.search,
page: pagination.current_page,
}));
// O fetch será refeito sempre que pagination.search ou pagination.current_page mudar.`
const { data } = useApiFetch('newsAll/', {
params: apiParams
});
Use a opção watch para informar um array de fontes reativas que devem ser observadas. É ideal para controle granular ou para observar fontes que não estão nos params.
> ⚠️ Importante: Para observar propriedades de um objeto reativo (como pagination), você deve passá-las como uma função getter: () => pagination.search.
>
`jsx`
const { data } = useApiFetch('newsAll/', {
watch: [
() => pagination.current_page,
() => pagination.search,
]
});
Em casos onde você quer passar um objeto de parâmetros completo, mas quer que o refetch aconteça apenas por mudanças em algumas chaves, use o seguinte padrão:
1. Passe params como uma função getter para sempre enviar os dados mais recentes.paramsReactives: false
2. Desative a reatividade automática com .refetch
3. Forneça as fontes reativas que devem disparar o no array watch.
`jsx
const { data } = useApiFetch('newsAll/', {
// 1. Fornece os dados mais recentes quando o fetch é executado
params: () => ({
page: pagination.current_page,
page_size: 10,
search: pagination.search,
filter: pagination.filter,
}),
// 2. Desliga a "escuta" automática do objeto params
paramsReactives: false,
// 3. Define explicitamente o que deve ser escutado para refazer a chamada
watch: [
() => pagination.current_page,
() => pagination.search,
]
});
`
Após realizar uma ação que modifica os dados no servidor (como criar, editar ou deletar um item), é essencial atualizar a lista de dados exibida na tela para refletir essa mudança.
O useApiFetch oferece duas maneiras principais para acionar um "refetch": uma manual (usando a função execute) e uma reativa (usando um "gatilho").
Esta é a abordagem mais direta. O composable retorna uma função execute que você pode chamar a qualquer momento para refazer a requisição com os parâmetros mais recentes.
1. Obtenha a função execute ao desestruturar o retorno do useApiFetch.execute()
2. Chame sempre que uma ação for concluída com sucesso.
`html
`
- Prós: Simples e direto.
- Contras: É um estilo mais "imperativo" (você manda a busca acontecer). Pode levar a chamadas repetidas de execute() em vários lugares do seu código.
Esta é a maneira mais limpa e "Vue" de lidar com o problema. Em vez de chamar uma função, você cria uma variável reativa (ref) que funciona como um gatilho. Você adiciona esse gatilho ao watch do useApiFetch e, para refazer a busca, você simplesmente altera o valor do gatilho.
No seu
`
Este padrão garante que sua interface de usuário esteja sempre sincronizada com os dados do servidor.
talvez você precise de usar mais de uma api, assim as requisições serão feitas para urls diferentes, isso pode ser resolvido simplesmente criando uma nova Instância nomeada
Talvez você precise usar mais de uma API, fazendo com que as requisições sejam feitas para URLs diferentes. Isso pode ser resolvido simplesmente criando uma nova Instância Nomeada.
O AxiosPlugin configura a sua API principal (padrão), enquanto a função createApiInstance permite registrar quantas APIs adicionais você precisar, cada uma com seu próprio nome, URL e interceptadores.
Primeiro, configure o plugin principal normalmente. Depois, importe e chame createApiInstance para cada API adicional.
`jsx
import { AxiosPlugin, createApiInstance } from 'v-api-fetch';
const app = createApp(App);
// --- 1. INSTÂNCIA PRINCIPAL (PADRÃO) ---
// Será acessada por padrão ou pelo nome 'api'
app.use(AxiosPlugin, {
// Configurações da API, lidas do ambiente
hostApi: import.meta.env.VITE_BASE_URL_API || 'http://localhost:8022',
subpath: import.meta.env.VITE_SUB_PATH || '',
baseApi: import.meta.env.VITE_BASE_API || '/api/v1',
requestInterceptor: (config) => { / ... / },
errorInterceptor: createErrorInterceptor(),
});
// --- 2. INSTÂNCIA ADICIONAL NOMEADA ---
// Função de criação de Instância
createApiInstance(app,
{
name: 'alguma-api',
hostApi: import.meta.env.VITE_BASE_URL_API_2 || 'http://localhost:8025',
subpath: import.meta.env.VITE_SUB_PATH_2 || '',
baseApi: import.meta.env.VITE_BASE_API_2 || '/api/v1',
requestInterceptor: (config) => { / ... / },
errorInterceptor: createErrorInterceptor(),
}
)
`
Uma vez que a instância nomeada é criada ('alguma-api' no exemplo acima), você pode acessá-la em qualquer componente passando o nome dela como o último argumento para os composables useApi e useApiFetch.
Para buscar dados de uma instância específica, informe o nome dela.
`jsx`
O mesmo princípio se aplica para realizar ações (POST, DELETE, etc.).
`jsx`$3
Agora é possível desabilitar todas as requisições passando a opção disable_request no useApiFetch.
Exemplo:
`html`
Agora é possível não executar imediatamente a requisição, o uso é simples e intuitivo:
`html``