Consumo de API REST desde React con Fetch o Axios
React ofrece múltiples opciones para realizar peticiones HTTP a nuestras APIs REST de Spring Boot. Las dos aproximaciones más populares son Fetch API (nativa del navegador) y Axios (biblioteca externa). Ambas permiten comunicación asíncrona eficiente con el backend.
Configuración inicial del proyecto React
Antes de consumir APIs, necesitamos crear nuestro proyecto React usando Vite, que es la herramienta moderna recomendada por su rapidez y eficiencia:
npm create vite@latest mi-app-react -- --template react
cd mi-app-react
npm install
npm run dev
Este comando crea un proyecto React optimizado que se ejecuta en http://localhost:5173 por defecto.
Consumo de APIs con Fetch API
Fetch es la opción nativa del navegador, incluida por defecto en React 19. No requiere dependencias adicionales y ofrece un control granular sobre las peticiones HTTP.
Ejemplo básico con GET:
import { useState, useEffect } from 'react';
function ListaUsuarios() {
const [usuarios, setUsuarios] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const obtenerUsuarios = async () => {
try {
const response = await fetch('http://localhost:8080/api/usuarios');
if (!response.ok) {
throw new Error(`Error: ${response.status}`);
}
const data = await response.json();
setUsuarios(data);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
obtenerUsuarios();
}, []);
if (loading) return <div>Cargando...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{usuarios.map(usuario => (
<li key={usuario.id}>{usuario.nombre}</li>
))}
</ul>
);
}
Ejemplo con POST para crear datos:
function CrearUsuario() {
const [nombre, setNombre] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch('http://localhost:8080/api/usuarios', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ nombre, email })
});
if (!response.ok) {
throw new Error('Error al crear usuario');
}
const nuevoUsuario = await response.json();
console.log('Usuario creado:', nuevoUsuario);
// Limpiar formulario
setNombre('');
setEmail('');
} catch (error) {
console.error('Error:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={nombre}
onChange={(e) => setNombre(e.target.value)}
placeholder="Nombre"
required
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<button type="submit">Crear Usuario</button>
</form>
);
}
Consumo de APIs con Axios
Axios es una biblioteca externa que simplifica las peticiones HTTP y ofrece funcionalidades adicionales como interceptores, transformación automática de datos y mejor manejo de errores.
Instalación de Axios:
npm install axios
Ejemplo básico con GET:
import { useState, useEffect } from 'react';
import axios from 'axios';
function ListaProductos() {
const [productos, setProductos] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const obtenerProductos = async () => {
try {
const response = await axios.get('http://localhost:8080/api/productos');
setProductos(response.data);
} catch (error) {
console.error('Error al obtener productos:', error);
} finally {
setLoading(false);
}
};
obtenerProductos();
}, []);
return (
<div>
{loading ? (
<p>Cargando productos...</p>
) : (
<div>
{productos.map(producto => (
<div key={producto.id}>
<h3>{producto.nombre}</h3>
<p>Precio: ${producto.precio}</p>
</div>
))}
</div>
)}
</div>
);
}
Ejemplo con operaciones CRUD completas:
import { useState, useEffect } from 'react';
import axios from 'axios';
// Configuración base de Axios
const api = axios.create({
baseURL: 'http://localhost:8080/api',
headers: {
'Content-Type': 'application/json'
}
});
function GestorTareas() {
const [tareas, setTareas] = useState([]);
const [nuevaTarea, setNuevaTarea] = useState('');
// Obtener todas las tareas
const obtenerTareas = async () => {
try {
const response = await api.get('/tareas');
setTareas(response.data);
} catch (error) {
console.error('Error al obtener tareas:', error);
}
};
// Crear nueva tarea
const crearTarea = async () => {
if (!nuevaTarea.trim()) return;
try {
const response = await api.post('/tareas', {
titulo: nuevaTarea,
completada: false
});
setTareas([...tareas, response.data]);
setNuevaTarea('');
} catch (error) {
console.error('Error al crear tarea:', error);
}
};
// Actualizar tarea
const toggleTarea = async (id, completada) => {
try {
const response = await api.put(`/tareas/${id}`, { completada: !completada });
setTareas(tareas.map(tarea =>
tarea.id === id ? response.data : tarea
));
} catch (error) {
console.error('Error al actualizar tarea:', error);
}
};
// Eliminar tarea
const eliminarTarea = async (id) => {
try {
await api.delete(`/tareas/${id}`);
setTareas(tareas.filter(tarea => tarea.id !== id));
} catch (error) {
console.error('Error al eliminar tarea:', error);
}
};
useEffect(() => {
obtenerTareas();
}, []);
return (
<div>
<h2>Gestor de Tareas</h2>
<div>
<input
type="text"
value={nuevaTarea}
onChange={(e) => setNuevaTarea(e.target.value)}
placeholder="Nueva tarea"
/>
<button onClick={crearTarea}>Agregar</button>
</div>
<ul>
{tareas.map(tarea => (
<li key={tarea.id}>
<input
type="checkbox"
checked={tarea.completada}
onChange={() => toggleTarea(tarea.id, tarea.completada)}
/>
<span style={{
textDecoration: tarea.completada ? 'line-through' : 'none'
}}>
{tarea.titulo}
</span>
<button onClick={() => eliminarTarea(tarea.id)}>
Eliminar
</button>
</li>
))}
</ul>
</div>
);
}
Manejo de estados de carga y errores
Una práctica recomendada es crear un hook personalizado que centralice la lógica de peticiones HTTP:
import { useState, useEffect } from 'react';
import axios from 'axios';
function useApi(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await axios.get(url);
setData(response.data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// Uso del hook personalizado
function ComponenteEjemplo() {
const { data: usuarios, loading, error } = useApi('http://localhost:8080/api/usuarios');
if (loading) return <div>Cargando...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{usuarios?.map(usuario => (
<p key={usuario.id}>{usuario.nombre}</p>
))}
</div>
);
}
Comparación Fetch vs Axios
| Característica | Fetch | Axios |
|---------------|-------|-------|
| Tamaño | Nativo (0 KB) | ~13 KB minificado |
| Soporte navegadores | Moderno | Amplio + polyfills |
| Sintaxis JSON | response.json() | response.data |
| Interceptores | No | Sí |
| Timeout automático | No | Sí |
| Cancelación | AbortController | CancelToken |
Fetch es ideal para proyectos que priorizan el tamaño del bundle y tienen necesidades básicas de HTTP. Axios es preferible cuando necesitas funcionalidades avanzadas como interceptores, transformación de datos o compatibilidad amplia con navegadores.
Configuración CORS y despliegue integrado
La integración entre React y Spring Boot requiere configurar adecuadamente el intercambio de recursos entre dominios (CORS) y establecer una estrategia de despliegue que permita servir tanto la API como el frontend desde la misma aplicación o dominios diferentes.
Configuración CORS en Spring Boot
CORS (Cross-Origin Resource Sharing) es esencial cuando el frontend React (puerto 5173) consume APIs del backend Spring Boot (puerto 8080). Sin la configuración adecuada, los navegadores bloquearán las peticiones por políticas de seguridad.
Configuración básica con @CrossOrigin:
@RestController
@RequestMapping("/api/usuarios")
@CrossOrigin(origins = "http://localhost:5173")
public class UsuarioController {
@Autowired
private UsuarioService usuarioService;
@GetMapping
public List<Usuario> obtenerUsuarios() {
return usuarioService.findAll();
}
@PostMapping
public ResponseEntity<Usuario> crearUsuario(@RequestBody Usuario usuario) {
Usuario nuevoUsuario = usuarioService.save(usuario);
return ResponseEntity.status(HttpStatus.CREATED).body(nuevoUsuario);
}
}
Configuración global con CorsConfigurationSource:
Para proyectos más complejos, es recomendable configurar CORS de manera global mediante una clase de configuración:
@Configuration
@EnableWebSecurity
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// Orígenes permitidos (desarrollo y producción)
configuration.setAllowedOriginPatterns(Arrays.asList(
"http://localhost:3000",
"http://localhost:5173",
"https://midominio.com"
));
// Métodos HTTP permitidos
configuration.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "DELETE", "OPTIONS"
));
// Headers permitidos
configuration.setAllowedHeaders(Arrays.asList(
"Authorization", "Content-Type", "X-Requested-With"
));
// Permitir envío de cookies y headers de autenticación
configuration.setAllowCredentials(true);
// Tiempo de cache para preflight requests
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
}
Integración con Spring Security:
Si utilizas Spring Security, debes configurar CORS dentro de la cadena de filtros de seguridad:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private CorsConfigurationSource corsConfigurationSource;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource))
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/productos/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
Configuración de proxy en desarrollo
Durante el desarrollo, es común tener React en puerto 5173 y Spring Boot en puerto 8080. Vite permite configurar un proxy para evitar problemas de CORS y simular un entorno de producción.
Configuración en vite.config.js:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false,
// Opcional: reescribir la ruta si es necesario
// rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
Con esta configuración, las peticiones desde React a /api/usuarios se redirigirán automáticamente a http://localhost:8080/api/usuarios, eliminando la necesidad de especificar la URL completa en el código:
// En lugar de: fetch('http://localhost:8080/api/usuarios')
// Simplemente usar:
const response = await fetch('/api/usuarios');
Despliegue integrado con Spring Boot
Para producción, la estrategia más eficiente es servir los archivos estáticos de React directamente desde Spring Boot, eliminando la necesidad de un servidor web adicional.
Paso 1: Build de producción de React
cd mi-app-react
npm run build
Este comando genera una carpeta dist/ con todos los archivos optimizados para producción.
Paso 2: Copiar archivos al proyecto Spring Boot
Copia el contenido de dist/ a la carpeta src/main/resources/static/ de tu proyecto Spring Boot:
src/
└── main/
└── resources/
└── static/
├── index.html
├── assets/
│ ├── index-ABC123.js
│ └── index-DEF456.css
└── vite.svg
Paso 3: Configurar Spring Boot para servir el frontend
Crea un controlador que redirija todas las rutas del frontend al index.html:
@Controller
public class FrontendController {
// Servir la API REST normalmente
@RequestMapping("/api/**")
public void api() {
// Este método nunca se ejecuta, solo define el patrón
}
// Redirigir todas las demás rutas al frontend React
@RequestMapping(value = "/{path:^(?!api|actuator|static).*$}/**",
method = RequestMethod.GET)
public String forward() {
return "forward:/index.html";
}
// Servir la página principal
@RequestMapping("/")
public String index() {
return "forward:/index.html";
}
}
Configuración alternativa con WebMvcConfigurer:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// Redirigir rutas del frontend a index.html
registry.addViewController("/").setViewName("forward:/index.html");
registry.addViewController("/{spring:[a-zA-Z0-9-_]+}")
.setViewName("forward:/index.html");
registry.addViewController("/**/{spring:[a-zA-Z0-9-_]+}")
.setViewName("forward:/index.html");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Servir archivos estáticos
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/static/");
// Servir assets de Vite
registry.addResourceHandler("/assets/**")
.addResourceLocations("classpath:/static/assets/");
}
}
Automatización del despliegue
Para automatizar el proceso de build y despliegue, puedes usar Maven o Gradle con plugins específicos:
Configuración Maven con frontend-maven-plugin:
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.15.0</version>
<configuration>
<workingDirectory>frontend</workingDirectory>
<installDirectory>target</installDirectory>
</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.11.0</nodeVersion>
<npmVersion>10.4.0</npmVersion>
</configuration>
</execution>
<!-- Instalar dependencias -->
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<!-- Build del frontend -->
<execution>
<id>npm run build</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
<!-- Copiar archivos build a resources/static -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>copy-frontend-build</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}/static</outputDirectory>
<resources>
<resource>
<directory>frontend/dist</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
Configuración de entornos múltiples
Para manejar diferentes entornos (desarrollo, testing, producción), utiliza variables de entorno y archivos de configuración específicos:
Archivo .env en React:
# .env.development
VITE_API_BASE_URL=http://localhost:8080/api
# .env.production
VITE_API_BASE_URL=/api
Uso en componentes React:
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
const fetchUsuarios = async () => {
const response = await fetch(`${API_BASE_URL}/usuarios`);
return response.json();
};
Configuración Spring Boot por perfiles:
# application.yml
spring:
profiles:
active: development
---
# application-development.yml
spring:
config:
activate:
on-profile: development
server:
port: 8080
cors:
allowed-origins:
- http://localhost:5173
- http://localhost:3000
---
# application-production.yml
spring:
config:
activate:
on-profile: production
server:
port: 8080
cors:
allowed-origins:
- https://midominio.com
Con esta configuración integrada, tendrás un despliegue único que sirve tanto la API REST como el frontend React, simplificando la infraestructura y mejorando el rendimiento al eliminar las peticiones cross-origin en producción.
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 React usando Fetch y Axios.
- Configurar CORS en Spring Boot para permitir la comunicación con React.
- Implementar un proxy en Vite para facilitar el desarrollo sin problemas de CORS.
- Desplegar una aplicación React integrada en un proyecto Spring Boot para producción.
- Gestionar entornos múltiples y automatizar el proceso de build y despliegue.