Shipping a Laravel + Angular Monolith Without Losing Your Mind
How I structure a single repo that ships a Laravel API, an Angular SPA, and a shared TypeScript contract — without resorting to a microservices manifesto. Real folder layout, real CI, real tradeoffs.
Shipping a Laravel + Angular Monolith
Most teams reach for microservices the moment their second frontend shows up. After shipping three Laravel + Angular projects this year, I am here to tell you that the boring monolith — done deliberately — will outperform a premature microservices setup on every axis that matters: developer velocity, deployment risk, observability, and cost.
The folder layout that actually works
I keep one git repository per product. Inside it:
/apps
/api -> Laravel 11 (REST + queues)
/web -> Angular 17 standalone components
/admin -> optional Angular shell
/packages
/contracts -> TypeScript types shared with the API
/ui -> shared presentational components
/docker
/infra
The packages/contracts directory is the secret. Laravel exposes its API shape via Scribe or a hand-rolled OpenAPI spec, and a tiny pnpm script regenerates TypeScript types into packages/contracts on every push to main. The Angular app imports those types directly. No drift. No any. No "the API changed and nobody told the frontend" incidents.
The three rules I refuse to break
- One deploy target per product. Laravel serves the Angular build from
public/in production. Two apps, one deploy. No CORS. No separate origin story. - Queues are not optional. Anything that touches a third party (email, payments, webhooks) goes on a Laravel queue. The HTTP request returns 200 in under 200ms, period.
- The database is the contract. Migrations are reviewed like application code. No "quick ALTER in prod" — ever.
CI, in 90 seconds
GitHub Actions runs three jobs in parallel on every push: phpunit, jest (Angular), and phpstan. A fourth job regenerates the contracts package and fails if the diff is non-empty — that catches forgotten spec updates before they ship.
When all four are green, a single Docker image is built and pushed. No "frontend pipeline" and "backend pipeline" — one artifact, one deploy.
When this stops working
I would reach for a split the day one of these becomes true: a second team needs to ship backend changes independently, the deploy image crosses ~15 minutes, or the queue backlog starts affecting web requests through shared database connections. None of that has happened yet.
Until then, the monolith ships faster, debugs easier, and lets me close the laptop at 6pm.