Consumiendo APIs REST en Vue 3 con Axios y Fetch: Guía Completa
Este tutorial te guiará a través del proceso de consumir APIs REST en tus proyectos de Vue 3. Exploraremos cómo realizar peticiones HTTP utilizando tanto la API nativa Fetch como la popular librería Axios, cubriendo GET, POST, PUT, DELETE y el manejo de errores.
🚀 Introducción: La Importancia de las APIs en Aplicaciones Vue
Las aplicaciones modernas rara vez son estáticas. La capacidad de interactuar con servicios externos, como las APIs RESTful, es fundamental para construir experiencias dinámicas y ricas en datos. Ya sea que estés mostrando datos de una base de datos, enviando información de formularios, o integrando servicios de terceros, consumir APIs es una habilidad esencial para cualquier desarrollador Vue.js.
En este tutorial, profundizaremos en cómo realizar peticiones HTTP en Vue 3, utilizando dos de las herramientas más comunes: la API nativa Fetch y la librería Axios. Cubriremos los métodos HTTP más importantes (GET, POST, PUT, DELETE) y te mostraremos cómo manejar respuestas y errores de forma efectiva.
¿Por qué Consumir APIs?
- Interactividad Dinámica: Traer y enviar datos en tiempo real.
- Separación de Preocupaciones: Mantener la lógica de backend separada del frontend.
- Reutilización de Datos: Acceder a la misma fuente de datos desde múltiples clientes (web, móvil).
- Integración: Conectar tu aplicación con servicios externos (pagos, autenticación, mapas, etc.).
🛠️ Preparando el Entorno: Tu Proyecto Vue 3
Antes de sumergirnos en el código, asegúrate de tener un proyecto Vue 3 configurado. Si aún no lo tienes, puedes crearlo rápidamente usando Vue CLI o Vite.
# Con Vite
npm create vue@latest
# Sigue las instrucciones, elige Vue Router, Pinia, etc. según tus necesidades
cd tu-proyecto-vue
npm install
npm run dev
Una vez que tu proyecto esté funcionando, puedes crear un nuevo componente o usar uno existente para practicar.
🌐 Consumiendo APIs con la API Nativa Fetch
La API Fetch es una interfaz moderna y potente para hacer peticiones de red, disponible de forma nativa en la mayoría de los navegadores actuales. Devuelve una Promise, lo que la hace ideal para usar con async/await.
➡️ Realizando Peticiones GET con Fetch
Comencemos con el método GET para obtener datos. Crearemos un componente simple que carga una lista de usuarios desde una API de ejemplo.
<template>
<div>
<h2>Usuarios (Fetch API)</h2>
<div v-if="loading">Cargando usuarios...</div>
<div v-else-if="error">Error: {{ error }}</div>
<ul v-else>
<li v-for="user in users" :key="user.id">
{{ user.name }} ({{ user.email }})
</li>
</ul>
<button @click="fetchUsers">Recargar Usuarios</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const users = ref([]);
const loading = ref(false);
const error = ref(null);
const fetchUsers = async () => {
loading.value = true;
error.value = null;
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
users.value = data;
} catch (err) {
console.error('Error fetching users:', err);
error.value = err.message;
} finally {
loading.value = false;
}
};
// Cargar usuarios al montar el componente
fetchUsers();
</script>
<style scoped>
/* Estilos básicos */
ul {
list-style: none;
padding: 0;
}
li {
background-color: #f0f0f0;
margin-bottom: 5px;
padding: 10px;
border-radius: 5px;
}
</style>
Explicación:
refpara estado: Usamosrefparausers,loadingyerrorpara que sean reactivos.fetchUsersasíncrona: La función esasyncpara poder usarawait.fetch(): Realiza la petición a la URL. Devuelve unaPromise.response.ok: Verifica si la respuesta HTTP fue exitosa (código 2xx). Si no, lanzamos un error.response.json(): Parsea el cuerpo de la respuesta como JSON. Esto también devuelve unaPromise.try...catch...finally: Es crucial para manejar posibles errores de red o del servidor y para resetear el estado de carga.fetchUsers()al inicio: Llamamos a la función directamente en elscript setuppara que se ejecute cuando el componente se monte, cargando los datos iniciales.
📝 Realizando Peticiones POST con Fetch
Para enviar datos a la API, usaremos el método POST. Necesitamos especificar el método, los encabezados (Content-Type) y el cuerpo de la petición.
<template>
<div>
<h2>Crear Usuario (Fetch API)</h2>
<form @submit.prevent="createUser">
<div>
<label for="name">Nombre:</label>
<input type="text" id="name" v-model="newUser.name" required />
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" v-model="newUser.email" required />
</div>
<button type="submit" :disabled="loading">Crear Usuario</button>
</form>
<div v-if="loading">Enviando usuario...</div>
<div v-if="postError" class="error-message">Error: {{ postError }}</div>
<div v-if="createdUser" class="success-message">
Usuario creado: {{ createdUser.name }} (ID: {{ createdUser.id }})
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const newUser = ref({
name: '',
email: ''
});
const createdUser = ref(null);
const loading = ref(false);
const postError = ref(null);
const createUser = async () => {
loading.value = true;
postError.value = null;
createdUser.value = null;
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newUser.value)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
createdUser.value = data;
// Opcional: limpiar formulario
newUser.value = { name: '', email: '' };
} catch (err) {
console.error('Error creating user:', err);
postError.value = err.message;
} finally {
loading.value = false;
}
};
</script>
<style scoped>
/* Estilos básicos para el formulario y mensajes */
form div {
margin-bottom: 10px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"], input[type="email"] {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.error-message {
color: red;
margin-top: 10px;
}
.success-message {
color: green;
margin-top: 10px;
}
</style>
Cambios clave para POST:
method: 'POST': Especifica el método HTTP.headers: { 'Content-Type': 'application/json' }: Es crucial para indicar al servidor que estamos enviando JSON.body: JSON.stringify(newUser.value): El cuerpo de la petición debe ser una cadena JSON. UsamosJSON.stringifypara convertir el objeto Vueref.
Otros Métodos (PUT/DELETE) con Fetch
Los métodos PUT (actualizar) y DELETE (eliminar) siguen una estructura similar a POST, pero típicamente incluyen el ID del recurso en la URL.
Ejemplo de PUT:
const updateUser = async (id, updatedData) => {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updatedData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Usuario actualizado:', data);
return data;
} catch (err) {
console.error('Error actualizando usuario:', err);
throw err; // Re-lanza el error para manejo externo
}
};
Ejemplo de DELETE:
const deleteUser = async (id) => {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log(`Usuario con ID ${id} eliminado.`);
// Típicamente, las respuestas DELETE exitosas no tienen cuerpo o devuelven un 204 No Content
} catch (err) {
console.error('Error eliminando usuario:', err);
throw err;
}
};
✨ Consumiendo APIs con Axios: Un Enfoque Más Sencillo
Axios es una librería cliente HTTP basada en promesas para el navegador y Node.js. Es muy popular debido a su API más concisa, interceptores de peticiones/respuestas, manejo automático de errores y conversión de JSON, entre otras características.
📥 Instalación de Axios
Primero, necesitamos instalar Axios en tu proyecto:
npm install axios
# o
yarn add axios
➡️ Realizando Peticiones GET con Axios
Ahora, veamos cómo la misma petición GET de usuarios se vería con Axios.
<template>
<div>
<h2>Usuarios (Axios)</h2>
<div v-if="loadingAxios">Cargando usuarios...</div>
<div v-else-if="errorAxios">Error: {{ errorAxios }}</div>
<ul v-else>
<li v-for="user in usersAxios" :key="user.id">
{{ user.name }} ({{ user.email }})
</li>
</ul>
<button @click="fetchUsersAxios">Recargar Usuarios</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
import axios from 'axios'; // Importa Axios
const usersAxios = ref([]);
const loadingAxios = ref(false);
const errorAxios = ref(null);
const fetchUsersAxios = async () => {
loadingAxios.value = true;
errorAxios.value = null;
try {
// Axios automáticamente parsea JSON y lanza errores para códigos de estado no 2xx
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
usersAxios.value = response.data; // Los datos están en response.data
} catch (err) {
console.error('Error fetching users with Axios:', err);
// El error de Axios contiene más detalles, como err.response.data, err.response.status
errorAxios.value = err.message;
if (err.response) {
errorAxios.value = `Error ${err.response.status}: ${err.response.statusText}`;
}
} finally {
loadingAxios.value = false;
}
};
fetchUsersAxios();
</script>
<style scoped>
/* Estilos básicos */
ul {
list-style: none;
padding: 0;
}
li {
background-color: #e6f7ff; /* Color diferente para distinguir */
margin-bottom: 5px;
padding: 10px;
border-radius: 5px;
}
</style>
Ventajas de Axios sobre Fetch aquí:
- Sintaxis más limpia:
axios.get(url)es más directo. - Manejo automático de JSON: No necesitas llamar a
response.json(). Los datos ya están disponibles enresponse.data. - Manejo automático de errores HTTP: Axios lanza un error por cualquier código de estado fuera del rango 2xx, lo que simplifica la lógica de verificación de
response.ok.
📝 Realizando Peticiones POST con Axios
El método POST también es más conciso con Axios.
<template>
<div>
<h2>Crear Usuario (Axios)</h2>
<form @submit.prevent="createUserAxios">
<div>
<label for="nameAxios">Nombre:</label>
<input type="text" id="nameAxios" v-model="newUserAxios.name" required />
</div>
<div>
<label for="emailAxios">Email:</label>
<input type="email" id="emailAxios" v-model="newUserAxios.email" required />
</div>
<button type="submit" :disabled="loadingAxiosPost">Crear Usuario</button>
</form>
<div v-if="loadingAxiosPost">Enviando usuario...</div>
<div v-if="postErrorAxios" class="error-message">Error: {{ postErrorAxios }}</div>
<div v-if="createdUserAxios" class="success-message">
Usuario creado: {{ createdUserAxios.name }} (ID: {{ createdUserAxios.id }})
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import axios from 'axios';
const newUserAxios = ref({
name: '',
email: ''
});
const createdUserAxios = ref(null);
const loadingAxiosPost = ref(false);
const postErrorAxios = ref(null);
const createUserAxios = async () => {
loadingAxiosPost.value = true;
postErrorAxios.value = null;
createdUserAxios.value = null;
try {
// Axios automáticamente serializa el objeto a JSON y setea el Content-Type
const response = await axios.post('https://jsonplaceholder.typicode.com/users', newUserAxios.value);
createdUserAxios.value = response.data;
newUserAxios.value = { name: '', email: '' };
} catch (err) {
console.error('Error creating user with Axios:', err);
postErrorAxios.value = err.message;
if (err.response) {
postErrorAxios.value = `Error ${err.response.status}: ${err.response.statusText}`;
}
} finally {
loadingAxiosPost.value = false;
}
};
</script>
<style scoped>
/* Estilos similares a los anteriores */
</style>
Cambios clave para POST con Axios:
axios.post(url, data): Pasamos el objeto de datos directamente como segundo argumento. Axios se encarga de serializarlo a JSON y de establecer el encabezadoContent-Type: application/json.
Otros Métodos (PUT/DELETE) con Axios
Axios también ofrece métodos directos para PUT y DELETE.
Ejemplo de PUT:
const updateUserAxios = async (id, updatedData) => {
try {
const response = await axios.put(`https://jsonplaceholder.typicode.com/users/${id}`, updatedData);
console.log('Usuario actualizado (Axios):', response.data);
return response.data;
} catch (err) {
console.error('Error actualizando usuario con Axios:', err);
throw err;
}
};
Ejemplo de DELETE:
const deleteUserAxios = async (id) => {
try {
await axios.delete(`https://jsonplaceholder.typicode.com/users/${id}`);
console.log(`Usuario con ID ${id} eliminado (Axios).`);
} catch (err) {
console.error('Error eliminando usuario con Axios:', err);
throw err;
}
};
⚙️ Configuración Global y Reutilización de Axios
Para aplicaciones más grandes, es buena práctica crear una instancia de Axios con configuraciones predeterminadas y luego usar esa instancia en toda la aplicación. Esto ayuda a gestionar cosas como URLs base, encabezados comunes y tokens de autenticación.
Creando una Instancia de Axios
Crea un archivo, por ejemplo, src/services/api.js:
// src/services/api.js
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com/', // Tu URL base de API
headers: {
'Content-type': 'application/json',
// 'Authorization': 'Bearer YOUR_AUTH_TOKEN' // Si necesitas autenticación
}
});
// Interceptor de peticiones (opcional)
apiClient.interceptors.request.use(
(config) => {
// console.log('Petición enviada:', config.url);
// Puedes añadir un token de autenticación aquí antes de cada petición
// const token = localStorage.getItem('authToken');
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Interceptor de respuestas (opcional)
apiClient.interceptors.response.use(
(response) => {
// console.log('Respuesta recibida:', response.config.url);
return response;
},
(error) => {
// Manejo global de errores, por ejemplo, redirigir al login si es 401
// if (error.response && error.response.status === 401) {
// router.push('/login');
// }
return Promise.reject(error);
}
);
export default apiClient;
Usando la Instancia en Componentes
Ahora, en tus componentes, puedes importar y usar apiClient:
// En tu componente Vue
import { ref } from 'vue';
import apiClient from '@/services/api'; // Ajusta la ruta si es necesario
const users = ref([]);
const loading = ref(false);
const error = ref(null);
const fetchUsers = async () => {
loading.value = true;
error.value = null;
try {
const response = await apiClient.get('/users'); // Ya no necesitas la URL base completa
users.value = response.data;
} catch (err) {
error.value = err.message;
if (err.response) {
error.value = `Error ${err.response.status}: ${err.response.statusText}`;
}
} finally {
loading.value = false;
}
};
fetchUsers();
🛡️ Manejo de Errores y Estados de Carga
Un aspecto crucial al trabajar con APIs es el manejo de errores y proporcionar retroalimentación al usuario sobre el estado de la petición (cargando, éxito, error).
Estados Comunes
| Estado | Descripción | Qué mostrar al usuario |
|---|---|---|
| --- | --- | --- |
loading | La petición está en curso | Un spinner, mensaje "Cargando..." |
success | La petición se completó correctamente | Los datos solicitados |
| --- | --- | --- |
error | Hubo un problema | Un mensaje de error claro, botones para reintentar |
Estrategias de Manejo de Errores
try...catch: Siempre envuelve tus peticionesasync/awaiten bloquestry...catchpara capturar errores de red o HTTP.- Verificación
response.ok(Fetch): Para Fetch, es vital verificarresponse.okpara errores HTTP que no lanzan una excepción de red. - Propiedades de error de Axios: Axios proporciona
error.response,error.requestyerror.messagepara un diagnóstico más detallado. - Mensajes al usuario: Traduce los errores técnicos en mensajes amigables para el usuario.
Diagrama de Flujo de una Petición API
🆚 Fetch vs. Axios: ¿Cuál Elegir?
Ambas herramientas son excelentes, pero tienen sus propias ventajas. La elección a menudo depende de las preferencias personales y las necesidades del proyecto.
Tabla Comparativa
| Característica | Fetch API | Axios |
|---|---|---|
| --- | --- | --- |
| Soporte de Navegador | Nativa (modernos) | Librería externa (requiere instalación) |
| API | Basada en Promesas, requiere .json() | Basada en Promesas, datos en response.data |
| --- | --- | --- |
| Manejo de Errores | Manual (response.ok), no lanza en 4xx/5xx | Automático (lanza para 4xx/5xx), detalles en error.response |
| Transformación de Datos | Manual (JSON.stringify, .json()) | Automática (JSON por defecto) |
| --- | --- | --- |
| Interceptores | No nativo (requiere envolver funciones) | Soporte nativo y potente |
| Subida de Archivos | FormData manual | Más sencilla con FormData |
| --- | --- | --- |
| Cancelación de Petición | AbortController nativo | Interfaz CancelToken o AbortController (v0.22+) |
| Progreso de Carga | No nativo | Soporte para progreso de subida/bajada |
¿Cuándo usar cada uno?
-
Usa Fetch si:
- Necesitas una solución ligera y sin dependencias externas.
- Solo realizas peticiones HTTP básicas.
- Estás trabajando en un entorno donde el tamaño del bundle es crítico.
-
Usa Axios si:
- Necesitas características avanzadas como interceptores.
- Quieres una API más consistente y amigable.
- Trabajas con autenticación (tokens) o manejo global de errores.
- El proyecto es de mediana a gran escala.
🎯 Próximos Pasos y Mejores Prácticas
Has aprendido los fundamentos para consumir APIs en Vue 3. Aquí hay algunas ideas para llevar tus conocimientos al siguiente nivel:
- Estados de Carga: Implementa indicadores visuales de carga (spinners, esqueletos) para una mejor experiencia de usuario.
- Manejo Centralizado de Errores: Utiliza los interceptores de Axios para mostrar notificaciones de error globales (ej. con librerías como
vue-toastificationoprimevue/toast). - Refactorización con Composables: Extrae tu lógica de consumo de API a composables para reutilizarla fácilmente en múltiples componentes.
// src/composables/useApi.js
import { ref } from 'vue';
import apiClient from '@/services/api';
export function useApi(url) {
const data = ref(null);
const loading = ref(false);
const error = ref(null);
const fetchData = async () => {
loading.value = true;
error.value = null;
try {
const response = await apiClient.get(url);
data.value = response.data;
} catch (err) {
error.value = err.message;
if (err.response) {
error.value = `Error ${err.response.status}: ${err.response.statusText}`;
}
} finally {
loading.value = false;
}
};
return { data, loading, error, fetchData };
}
**Uso en componente:**
<template>
<div>
<h3>Datos con Composable</h3>
<div v-if="loading">Cargando...</div>
<div v-else-if="error">Error: {{ error }}</div>
<pre v-else>{{ data }}</pre>
<button @click="fetchData">Cargar</button>
</div>
</template>
<script setup>
import { useApi } from '@/composables/useApi';
const { data, loading, error, fetchData } = useApi('/todos/1');
fetchData(); // Cargar al inicio
</script>
- Tokens de Autenticación: Implementa el almacenamiento y el envío de tokens JWT (JSON Web Tokens) en los encabezados de tus peticiones para proteger tus APIs.
- Variables de Entorno: Utiliza variables de entorno (
.env) para gestionar tus URLs de API base, diferenciando entre entornos de desarrollo, staging y producción.
🔚 Conclusión
Dominar el consumo de APIs es un pilar fundamental en el desarrollo de aplicaciones web modernas con Vue 3. Ya sea que optes por la simplicidad de la API nativa Fetch o la potencia y conveniencia de Axios, ahora tienes las herramientas y el conocimiento para integrar tu frontend con cualquier servicio RESTful. Experimenta, construye y sigue explorando las vastas posibilidades que la interconexión con APIs ofrece.
¡Feliz codificación! 👨💻
Tutoriales relacionados
- Validación de Formularios Reactivos en Vue 3 con VeeValidate y Yup: Guía Completaintermediate20 min
- Gestión de Estado Centralizada con Pinia en Vue 3: Guía Completaintermediate18 min
- Desarrollo de Componentes Reutilizables y Extendibles en Vue 3 con Slots y Composablesintermediate20 min
- Migrando de Options API a Composition API en Vue 3: Una Guía Prácticaintermediate15 min
- Controlando la Visibilidad: Directivas v-if, v-show y v-for en Vue 3 para Renderizado Condicional y Listasintermediate15 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!