Consumo de API REST desde Vue con Axios
Vue 3.5 ofrece una arquitectura moderna basada en la Composition API que simplifica significativamente el consumo de servicios REST. Axios se mantiene como la biblioteca de referencia para realizar peticiones HTTP, proporcionando una interfaz intuitiva y funcionalidades avanzadas como interceptores y manejo automático de respuestas JSON.
Instalación y configuración inicial
Para integrar Axios en un proyecto Vue 3.5, primero instalamos la dependencia:
npm install axios
La configuración recomendada consiste en crear una instancia configurada de Axios que centralize la configuración base:
// src/services/api.js
import axios from 'axios'
const api = axios.create({
baseURL: 'http://localhost:8080/api',
timeout: 5000,
headers: {
'Content-Type': 'application/json'
}
})
export default api
Esta configuración establece la URL base del backend Spring Boot y define headers comunes para todas las peticiones.
Implementación de servicios con Composition API
Vue 3.5 recomienda organizar las llamadas a la API mediante composables reutilizables. Un composable encapsula la lógica de estado y las operaciones asíncronas:
// src/composables/useProducts.js
import { ref, reactive } from 'vue'
import api from '@/services/api'
export function useProducts() {
const products = ref([])
const loading = ref(false)
const error = ref(null)
const fetchProducts = async () => {
loading.value = true
error.value = null
try {
const response = await api.get('/productos')
products.value = response.data
} catch (err) {
error.value = err.response?.data?.message || 'Error al cargar productos'
} finally {
loading.value = false
}
}
const createProduct = async (productData) => {
try {
const response = await api.post('/productos', productData)
products.value.push(response.data)
return response.data
} catch (err) {
error.value = err.response?.data?.message || 'Error al crear producto'
throw err
}
}
return {
products,
loading,
error,
fetchProducts,
createProduct
}
}
Consumo en componentes Vue
Los composables se integran naturalmente en componentes Vue usando la Composition API:
<template>
<div class="products-container">
<div v-if="loading" class="loading">
Cargando productos...
</div>
<div v-else-if="error" class="error">
{{ error }}
</div>
<div v-else class="products-grid">
<div
v-for="product in products"
:key="product.id"
class="product-card"
>
<h3>{{ product.nombre }}</h3>
<p>{{ product.descripcion }}</p>
<span class="price">€{{ product.precio }}</span>
</div>
</div>
<button @click="loadProducts" :disabled="loading">
Recargar productos
</button>
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import { useProducts } from '@/composables/useProducts'
const { products, loading, error, fetchProducts } = useProducts()
const loadProducts = () => {
fetchProducts()
}
onMounted(() => {
fetchProducts()
})
</script>
Manejo de diferentes tipos de peticiones HTTP
Para operaciones CRUD completas, podemos extender el composable con todos los métodos HTTP necesarios:
// src/composables/useProductsCrud.js
export function useProductsCrud() {
const products = ref([])
const loading = ref(false)
const error = ref(null)
// GET - Obtener producto por ID
const getProduct = async (id) => {
try {
const response = await api.get(`/productos/${id}`)
return response.data
} catch (err) {
error.value = `Error al obtener producto: ${err.message}`
throw err
}
}
// PUT - Actualizar producto completo
const updateProduct = async (id, productData) => {
try {
const response = await api.put(`/productos/${id}`, productData)
const index = products.value.findIndex(p => p.id === id)
if (index !== -1) {
products.value[index] = response.data
}
return response.data
} catch (err) {
error.value = `Error al actualizar: ${err.message}`
throw err
}
}
// DELETE - Eliminar producto
const deleteProduct = async (id) => {
try {
await api.delete(`/productos/${id}`)
products.value = products.value.filter(p => p.id !== id)
} catch (err) {
error.value = `Error al eliminar: ${err.message}`
throw err
}
}
return {
products,
loading,
error,
getProduct,
updateProduct,
deleteProduct
}
}
Gestión de formularios con peticiones POST
Para formularios que envían datos al backend, combinamos reactive con validación básica:
<template>
<form @submit.prevent="handleSubmit" class="product-form">
<div class="form-group">
<label for="nombre">Nombre:</label>
<input
id="nombre"
v-model="form.nombre"
type="text"
required
/>
</div>
<div class="form-group">
<label for="precio">Precio:</label>
<input
id="precio"
v-model.number="form.precio"
type="number"
step="0.01"
required
/>
</div>
<button type="submit" :disabled="loading">
{{ loading ? 'Guardando...' : 'Guardar Producto' }}
</button>
<div v-if="error" class="error">{{ error }}</div>
<div v-if="success" class="success">¡Producto creado exitosamente!</div>
</form>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { useProducts } from '@/composables/useProducts'
const { createProduct, loading, error } = useProducts()
const form = reactive({
nombre: '',
descripcion: '',
precio: 0
})
const success = ref(false)
const handleSubmit = async () => {
success.value = false
try {
await createProduct(form)
success.value = true
// Limpiar formulario
Object.assign(form, {
nombre: '',
descripcion: '',
precio: 0
})
} catch (err) {
console.error('Error al enviar formulario:', err)
}
}
</script>
Interceptores para manejo global
Los interceptores de Axios permiten procesar respuestas y errores de forma centralizada:
// src/services/api.js
import axios from 'axios'
const api = axios.create({
baseURL: 'http://localhost:8080/api',
timeout: 5000
})
// Interceptor de respuesta para manejo global de errores
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 404) {
console.error('Recurso no encontrado')
} else if (error.response?.status >= 500) {
console.error('Error del servidor')
}
return Promise.reject(error)
}
)
export default api
Esta configuración proporciona una base sólida para el consumo de APIs REST desde Vue 3.5, aprovechando las ventajas de la Composition API y las capacidades avanzadas de Axios para crear aplicaciones frontend robustas y mantenibles.
Configuración de CORS y build conjunto con Spring Boot
La integración completa entre Vue y Spring Boot requiere dos configuraciones esenciales: la habilitación de CORS para permitir peticiones cross-origin durante el desarrollo, y la configuración del build conjunto para servir la aplicación Vue desde el mismo servidor Spring Boot en producción.
Configuración de CORS en Spring Boot
CORS (Cross-Origin Resource Sharing) es necesario porque durante el desarrollo, Vue se ejecuta típicamente en http://localhost:5173 (Vite) mientras que Spring Boot corre en http://localhost:8080. Los navegadores bloquean estas peticiones cross-origin por seguridad.
Configuración global de CORS
La forma más eficiente de habilitar CORS es mediante una configuración global que aplique a todos los endpoints:
// src/main/java/com/example/config/CorsConfig.java
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:5173", "http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
};
}
}
Esta configuración permite peticiones desde los puertos comunes de desarrollo de Vue (Vite usa 5173, otros servidores pueden usar 3000) y habilita todos los métodos HTTP necesarios.
Configuración de CORS por controlador
Para casos específicos, podemos configurar CORS directamente en los controladores REST:
// src/main/java/com/example/controller/ProductoController.java
package com.example.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/productos")
@CrossOrigin(origins = {"http://localhost:5173", "http://localhost:3000"})
public class ProductoController {
@GetMapping
public List<Producto> obtenerProductos() {
return productoService.obtenerTodos();
}
@PostMapping
public Producto crearProducto(@RequestBody Producto producto) {
return productoService.crear(producto);
}
}
Configuración de CORS con Spring Security
Si la aplicación usa Spring Security, la configuración de CORS debe integrarse con la cadena de filtros de seguridad:
// src/main/java/com/example/config/SecurityConfig.java
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:5173"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
}
Build conjunto para producción
En producción, es recomendable servir la aplicación Vue directamente desde Spring Boot para simplificar el despliegue y evitar problemas de CORS.
Configuración del build de Vue
Primero, configuramos Vue para que genere los archivos estáticos en el directorio correcto de Spring Boot:
// vue.config.js (raíz del proyecto Vue)
module.exports = {
outputDir: '../backend/src/main/resources/static',
assetsDir: 'assets',
publicPath: '/',
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false
}
}
}
}
Si usas Vite (recomendado para Vue 3.5), la configuración equivalente sería:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
outDir: '../backend/src/main/resources/static',
assetsDir: 'assets'
},
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false
}
}
}
})
Automatización del build con Maven
Para automatizar el proceso de build, configuramos Maven para que compile Vue antes de generar el JAR de Spring Boot:
<!-- pom.xml -->
<build>
<plugins>
<!-- Plugin para ejecutar comandos Node.js -->
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.15.0</version>
<configuration>
<workingDirectory>../frontend</workingDirectory>
</configuration>
<executions>
<!-- Instalar Node.js y npm -->
<execution>
<id>install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<configuration>
<nodeVersion>v20.10.0</nodeVersion>
<npmVersion>10.2.3</npmVersion>
</configuration>
</execution>
<!-- Instalar dependencias -->
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<!-- Build de producción -->
<execution>
<id>npm run build</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Configuración del controlador para servir la aplicación Vue
Para que Spring Boot sirva correctamente la aplicación SPA de Vue, necesitamos un controlador que redirija todas las rutas no-API al index.html:
// src/main/java/com/example/controller/SpaController.java
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SpaController {
@RequestMapping(value = {
"/",
"/productos",
"/usuarios",
"/dashboard/**"
})
public String spa() {
return "forward:/index.html";
}
}
Configuración de recursos estáticos
Aseguramos que Spring Boot sirva correctamente los recursos estáticos de Vue:
// src/main/java/com/example/config/WebConfig.java
package com.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/assets/**")
.addResourceLocations("classpath:/static/assets/")
.setCachePeriod(31536000); // 1 año de cache para assets
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(0); // Sin cache para HTML
}
}
Flujo de desarrollo recomendado
Para un desarrollo eficiente, recomendamos este flujo:
1. Desarrollo local:
- Vue en
http://localhost:5173con proxy a Spring Boot - Spring Boot en
http://localhost:8080con CORS habilitado - Hot reload automático en ambos proyectos
2. Build de producción:
# Desde el directorio del frontend Vue
npm run build
# Desde el directorio del backend Spring Boot
mvn clean package
# El JAR resultante contiene tanto la API como la aplicación Vue
java -jar target/mi-aplicacion-0.0.1-SNAPSHOT.jar
Esta configuración permite un desarrollo ágil con hot reload mientras garantiza un despliegue simplificado en producción con una sola aplicación que sirve tanto la API REST como la interfaz de usuario Vue.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en SpringBoot
Documentación oficial de SpringBoot
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, SpringBoot es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de SpringBoot
Explora más contenido relacionado con SpringBoot y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender cómo consumir APIs REST desde Vue 3.5 utilizando Axios y la Composition API.
- Aprender a configurar CORS en Spring Boot para permitir peticiones cross-origin durante el desarrollo.
- Implementar composables en Vue para manejar operaciones CRUD y formularios con validación básica.
- Configurar el build conjunto de Vue y Spring Boot para un despliegue integrado en producción.
- Gestionar interceptores de Axios para un manejo global de respuestas y errores.