50% OFF Plus
--:--:--
¡Ver!

Integración con React

Avanzado
SpringBoot
SpringBoot
Actualizado: 08/10/2025

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 - Autor del tutorial

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.