aspnet-core-integracion-frontend-react-vanilla-html

Conectar React, Vue o HTML con ASP.NET Core

  • 5 min

La integración frontend-backend es la forma en que una interfaz web consume una API ASP.NET Core.

Ya tienes tu API funcionando, segura y documentada. Pero seamos sinceros: tus usuarios no van a usar curl ni Swagger para comprar productos. Necesitan una interfaz visual.

La pregunta del millón es: ¿Dónde pongo mi código de React/Angular/HTML? ¿Dentro del proyecto .NET? ¿En otro servidor?

Hoy vamos a ver las dos estrategias principales para casar el Frontend con el Backend.

Arquitectura desacoplada

Esta es una de las formas más habituales de trabajar con SPAs modernas.

  • Backend: Corre en el puerto 5000 (o en Azure/AWS).
  • Frontend: Es un proyecto separado (por ejemplo, creado con Vite o Angular CLI) que corre en otro puerto o dominio.

Son dos aplicaciones distintas que viven en repositorios distintos y solo se hablan por HTTP.

Flujo de desarrollo

Arrancas la API (dotnet run -> localhost:5000).

Arrancas el Frontend (npm run dev -> localhost:5173).

El Frontend hace peticiones fetch('http://localhost:5000/api/...').

Para que esto funcione, hay que tener bien configurado CORS en la API y permitir que localhost:5173 (o el dominio real del frontend) pueda pedir datos.

Ventajas

  • Especialización: Cada parte usa sus propias herramientas y flujo de trabajo.
  • Despliegue independiente: Puedes actualizar el frontend sin reiniciar el servidor backend.

Servir archivos estáticos

Esta estrategia es ideal para:

  • Proyectos pequeños o personales.
  • Aplicaciones corporativas internas (Intranet).
  • Vanilla HTML/JS donde no hay proceso de compilación complejo.

Aquí, ASP.NET Core sirve tanto la API como el HTML. El navegador descarga el HTML desde .NET y luego el JS llama a la API del mismo dominio.

La carpeta wwwroot

Por convención, ASP.NET Core busca los archivos estáticos (HTML, CSS, JS, Imágenes) en una carpeta llamada wwwroot en la raíz del proyecto.

Crea la carpeta y pon dentro un index.html:

<!DOCTYPE html>
<html>
<head>
    <title>Mi Tienda .NET</title>
</head>
<body>
    <h1>Productos</h1>
    <ul id="lista"></ul>

    <script>
        // Al estar en el mismo dominio, NO necesitamos poner http://localhost:5000
        // Usamos rutas relativas. ¡Adiós problemas de CORS!
        fetch('/api/productos')
            .then(res => res.json())
            .then(data => {
                const ul = document.getElementById('lista');
                data.forEach(p => {
                    const li = document.createElement('li');
                    li.textContent = p.nombre + " - " + p.precio + "€";
                    ul.appendChild(li);
                });
            });
    </script>
</body>
</html>
Copied!

Activar el middleware

En Program.cs, debemos decirle a la aplicación que permita servir estos archivos.

var app = builder.Build();

// ... otros middlewares ...

app.UseDefaultFiles(); // 1. Busca index.html o default.html
app.UseStaticFiles();  // 2. Permite servir archivos de wwwroot

app.MapControllers();
app.Run();
Copied!

Ahora, si vas a http://localhost:5000, verás tu web. Y la llamada a /api/productos funcionará instantáneamente sin configurar CORS, porque el origen es el mismo.

Integrar una SPA dentro de .NET

¿Qué pasa si quieres usar React pero quieres desplegar un solo artefacto (un solo .exe o contenedor Docker) que lo tenga todo?

Puedes compilar tu proyecto de React (npm run build) y copiar el resultado (la carpeta dist o build) dentro del wwwroot de .NET.

Hay un problema adicional: el routing. En una SPA (Single Page Application), tú navegas a /productos/5 y es React quien te muestra la vista. Pero si pulsas F5 (Refrescar), la petición llega al servidor .NET. .NET buscará un archivo o controlador llamado /productos/5, no lo encontrará y devolverá un 404 Not Found.

Fallback a index.html

Debemos decirle a .NET: “Si el usuario pide una ruta que NO es de la API y NO es un archivo físico (imagen/css), devuélvele siempre el index.html y que React se apañe”.

// Program.cs

app.UseStaticFiles(); // Sirve el JS y CSS de React

// Rutas de API
app.MapControllers();

// Comodín para SPA: Cualquier otra cosa -> index.html
app.MapFallbackToFile("index.html");
Copied!

Con esto, si refrescas en /clientes, .NET devuelve el index.html, React carga, lee la URL /clientes y muestra la pantalla correcta.

Consumir la API: buenas prácticas

Independientemente de la estrategia, cuando escribas tu código JavaScript (React/Vue/Vanilla), sigue estas reglas:

No fijes las URL en el código

❌ Mal:

fetch('http://localhost:5000/api/productos')
Copied!

Esto fallará cuando subas a producción, porque la URL no será localhost.

✅ Bien (Variables de Entorno en Vite/React): Crea un archivo .env:

VITE_API_URL=http://localhost:5000/api
Copied!

Y úsalo en tu código:

const baseUrl = import.meta.env.VITE_API_URL;
fetch(`${baseUrl}/productos`)
Copied!

Usa async y await

El código moderno es mucho más limpio que las promesas .then().

async function cargarProductos() {
    try {
        const response = await fetch('/api/productos');

        if (!response.ok) {
            throw new Error('Error al cargar');
        }

        const datos = await response.json();
        console.log(datos);
    } catch (error) {
        console.error("Algo explotó:", error);
    }
}
Copied!

Gestiona el token JWT

Si tu API usa un token Bearer, debes enviarlo en cada petición. Guardarlo en localStorage es sencillo, pero lo expone a cualquier script que consiga ejecutarse mediante XSS; una cookie HttpOnly evita ese acceso, aunque exige protegerse frente a CSRF. La estrategia depende del modelo de amenazas de la aplicación.

const token = localStorage.getItem('mi_token_jwt');

fetch('/api/perfil', {
    headers: {
        'Authorization': `Bearer ${token}` // 👈 La clave
    }
})
Copied!