Base full-stack autocontenida: una API REST en Symfony 7.4 y una SPA propia en React + Vite, con MySQL 8, autenticación por sesión (cookie HttpOnly) y un CRUD de tareas privadas por usuario. Todo levanta con un solo comando (clone & run), sin tener PHP, Composer ni Node instalados en el host.
Es un punto de partida para proyectos donde el backend Symfony y un frontend
React viven en repos/servicios separados y se comunican por HTTP. Está inspirado
en Boilerplate-Docker-Symfony,
pero es un demo autocontenido (no un entorno reusable).
No confundir con
symfony-api-platform-admin: ahí el frontend es un admin generado por API Platform. Acá el frontend es React escrito a mano y la API son controladores Symfony + Serializer.
navegador ──→ http://localhost:8099 (Vite dev server, SPA React)
│
│ Vite proxyea /api → http://api (Symfony)
▼
┌──────────┐ ┌──────────┐
│ api │ ─────→ │ db │
│ Symfony │ PDO │ MySQL 8 │
│ (Apache) │ └──────────┘
└──────────┘
El navegador habla siempre con un solo origen (localhost:8099): Vite
reenvía todo lo que empieza con /api al backend. Así la cookie de sesión
(HttpOnly) viaja en cada request sin depender de CORS.
| Servicio | Imagen / build | Puerto host | Rol |
|---|---|---|---|
frontend |
node:20-alpine |
8099 | SPA React + Vite (con proxy /api) |
api |
PHP 8.4 + Apache | 8100 | API REST Symfony en /api |
db |
mysql:8.0 |
3311 | Base de datos |
Requisitos: Docker y Docker Compose. Nada más.
git clone https://github.com/walteru/symfony-react-fullstack.git
cd symfony-react-fullstack
make start # o: docker compose up -dEn el primer arranque el backend instala dependencias (composer install),
espera a MySQL, aplica migraciones y carga datos demo; el frontend instala sus
node_modules. Puede tardar un minuto la primera vez.
- Frontend: http://localhost:8099
- API (health): http://localhost:8100/api/health
email: demo@example.com
password: demo1234
También podés crear una cuenta nueva desde el formulario.
- Sesión con cookie HttpOnly. El login no devuelve ningún token al
JavaScript; la sesión vive en una cookie que el navegador no puede leer
(mitiga XSS). La cookie es
HttpOnlyySameSite=Lax. - CSRF en todas las mutaciones. Como la sesión viaja por cookie, cada
POST/PUT/PATCH/DELETE(incluidos register, login y logout) exige la cabeceraX-CSRF-Token. El token se obtiene enGET /api/csrf-tokeny queda ligado a la sesión: un sitio atacante no puede leerlo. El frontend lo gestiona solo. - Datos privados por usuario. El CRUD opera siempre sobre las tareas del usuario autenticado. Una tarea ajena o inexistente devuelve 404 (no se distingue, para no filtrar la existencia de recursos de otros).
Códigos: 401 sin sesión · 403 falta/ inválido el CSRF · 404 recurso ajeno
o inexistente · 422 payload inválido.
make start # levanta todo
make test # corre la suite PHPUnit (sobre MySQL de tests aislado)
make cs # reporta desviaciones de estilo (PHP CS Fixer, sin tocar nada)
make cs-fix # corrige el estilo automáticamente (PHP CS Fixer)
make stan # análisis estático (PHPStan)
make check # estilo + análisis estático + tests (control de calidad completo)
make logs # logs en vivo
make reset-data # reinicia SOLO los datos de desarrollo (borra el volumen)
make down # baja contenedores (conserva los datos)
make rebuild # reconstruye desde ceromake testTests funcionales (WebTestCase) que cubren registro, login/logout, validación,
CSRF en mutaciones, contrato JSON y aislamiento entre usuarios (un usuario no
puede ver ni tocar tareas de otro). Corren contra una base MySQL de tests
separada (app_test).
Además de los tests, el backend usa dos herramientas de calidad que corren dentro del contenedor (no hace falta PHP ni Composer en el host):
- PHP CS Fixer — formatea el código con las reglas
@PSR12+@Symfony.make csreporta sin tocar nada;make cs-fixaplica los arreglos. La configuración está enbackend/.php-cs-fixer.dist.php. - PHPStan (nivel 6) — análisis estático que detecta errores de tipos sin
ejecutar el código. Con las extensiones de Symfony, Doctrine y PHPUnit
entiende el contenedor de servicios y los repositorios. La configuración está
en
backend/phpstan.dist.neon.
make check # cs + stan + test, lo mismo que correría un CILas dos son dependencias require-dev, así que se instalan solas en el primer
arranque (clone & run) junto con el resto.
El razonamiento detrás de la configuración (por qué nivel 6, por qué se acepta el patrón de propiedades nullable de Doctrine y qué aporta cada herramienta) está explicado acá: PHP CS Fixer y PHPStan: calidad automática en un proyecto Symfony.
Es una base full-stack con auth y CRUD, no un SaaS completo. Quedan fuera, a propósito: recuperación de contraseña, verificación por email, roles, refresh tokens, build de producción del frontend y despliegue. El proxy de Vite es de desarrollo: en un despliegue real, frontend y API se sirven detrás del mismo dominio (por ejemplo, con Nginx) para mantener el mismo origen.
La historia y las decisiones de diseño de este demo (por qué sesión + cookie HttpOnly en vez de un JWT en localStorage, cómo el proxy de Vite evita la pelea con CORS y por qué hay CSRF en todas las mutaciones) están explicadas acá: Symfony + React full-stack: conectar un frontend propio a una API con sesión y CSRF.
MIT — ver LICENSE.

