tutoriales.com

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.

Intermedio20 min de lectura13 views
Reportar error

🚀 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.

💡 Consejo: Se recomienda usar Vite para nuevos proyectos debido a su velocidad.
# 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:

  1. ref para estado: Usamos ref para users, loading y error para que sean reactivos.
  2. fetchUsers asíncrona: La función es async para poder usar await.
  3. fetch(): Realiza la petición a la URL. Devuelve una Promise.
  4. response.ok: Verifica si la respuesta HTTP fue exitosa (código 2xx). Si no, lanzamos un error.
  5. response.json(): Parsea el cuerpo de la respuesta como JSON. Esto también devuelve una Promise.
  6. try...catch...finally: Es crucial para manejar posibles errores de red o del servidor y para resetear el estado de carga.
  7. fetchUsers() al inicio: Llamamos a la función directamente en el script setup para 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. Usamos JSON.stringify para convertir el objeto Vue ref.
📌 Nota: `jsonplaceholder` es una API de pruebas que simula la creación, pero no guarda los datos. Siempre devolverá el objeto enviado con un ID.

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 en response.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 encabezado Content-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

EstadoDescripciónQué mostrar al usuario
---------
loadingLa petición está en cursoUn spinner, mensaje "Cargando..."
successLa petición se completó correctamenteLos datos solicitados
---------
errorHubo un problemaUn mensaje de error claro, botones para reintentar

Estrategias de Manejo de Errores

  • try...catch: Siempre envuelve tus peticiones async/await en bloques try...catch para capturar errores de red o HTTP.
  • Verificación response.ok (Fetch): Para Fetch, es vital verificar response.ok para errores HTTP que no lanzan una excepción de red.
  • Propiedades de error de Axios: Axios proporciona error.response, error.request y error.message para un diagnóstico más detallado.
  • Mensajes al usuario: Traduce los errores técnicos en mensajes amigables para el usuario.
⚠️ Advertencia: Nunca muestres mensajes de error internos o detalles técnicos del servidor directamente al usuario final. Podrían contener información sensible.

Diagrama de Flujo de una Petición API

Inicio Estado: Cargando Realizar Petición API ¿Petición Exitosa? NO Datos Recibidos Estado: Éxito Mostrar Datos Error Ocurrido Estado: Error Mostrar Mensaje Error Fin

🆚 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ísticaFetch APIAxios
---------
Soporte de NavegadorNativa (modernos)Librería externa (requiere instalación)
APIBasada en Promesas, requiere .json()Basada en Promesas, datos en response.data
---------
Manejo de ErroresManual (response.ok), no lanza en 4xx/5xxAutomático (lanza para 4xx/5xx), detalles en error.response
Transformación de DatosManual (JSON.stringify, .json())Automática (JSON por defecto)
---------
InterceptoresNo nativo (requiere envolver funciones)Soporte nativo y potente
Subida de ArchivosFormData manualMás sencilla con FormData
---------
Cancelación de PeticiónAbortController nativoInterfaz CancelToken o AbortController (v0.22+)
Progreso de CargaNo nativoSoporte 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.
🔥 Importante: Para la mayoría de los proyectos de Vue 3, Axios es la opción preferida por su comodidad y las características que ofrece, especialmente los interceptores que simplifican la gestión de autenticación y errores globales.

🎯 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:

  1. Estados de Carga: Implementa indicadores visuales de carga (spinners, esqueletos) para una mejor experiencia de usuario.
  2. Manejo Centralizado de Errores: Utiliza los interceptores de Axios para mostrar notificaciones de error globales (ej. con librerías como vue-toastification o primevue/toast).
  3. 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>
  1. 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.
  2. 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

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!