Skip to content

Architecture

Vroum Solutions is a modular Laravel 13 application with a Vue 3 + Inertia.js v3 frontend. It is multi-tenant by user context (Shop vs Client) within a single database rather than database-per-tenant. Access & scoping are enforced at the domain, middleware & permission layers.

High-Level Layers

  • Presentation: Vue 3 (Composition API) delivered via Inertia responses.
  • Transport: HTTP (RESTful routes), WebSockets (Laravel Reverb), Queues (Redis + Horizon).
  • Application: Actions (command-like units), Controllers (thin HTTP adapters), Policies (authorization gates).
  • Domain: Models, Value Objects, Enums, Data DTOs.
  • Infrastructure: PostgreSQL 17 (primary DB), Redis (cache, queue, broadcast), Meilisearch (search), S3-compatible storage (Flysystem / MinIO locally), Browsershot + Chromium (PDF), Webhooks.
  • Observability: Sentry (errors & performance), Laravel Pulse (app metrics), Nightwatch, Activity Log (Spatie), Ray (debugging in dev).

Directory Structure (Key)

app/
  Actions/        # Single-purpose classes using the AsAction trait
  Ai/             # LLM tools & agents (laravel/ai)
  Casts/          # Custom Eloquent casts (Money, …)
  Data/           # Spatie Data DTOs (typed payloads -> TS types)
  Entities/       # Backed enums → TS enums with metadata
  Facades/        # Facades over integration services (Carfax, NHTSA, SMS, …)
  Http/           # Thin controllers, middleware, form requests, Inertia response factory
  Models/         # Eloquent models (all extend App\Models\Model)
  Normalizers/    # Inbound payload normalization
  Policies/       # Authorization rules
  Rules/          # Custom validation rules
  Services/       # Cross-cutting or integration services
  Traits/         # Shared model behaviour (HasShop, ShopSearchable, Userstamps, …)
  Transformers/   # TypeScript transform customizations
  ValueObjects/   # Immutable domain representations
  Webhook/        # Webhook handlers
bootstrap/        # App bootstrap & middleware registration
config/           # Configuration for packages & features
resources/js/     # Frontend (Pages, Components, Elements, Layouts, Stores, types)
resources/css/    # Tailwind entry + theme tokens (see theme.md)
resources/icons/  # Source SVGs, compiled by npm run generate:icons
routes/           # Route files segmented by context
tests/            # PHPUnit test suites (Feature, Unit)

Request Flow

  1. HTTP request hits a route (e.g., routes/webShop.php).
  2. Route maps to a Controller or directly to an Action used as a controller.
  3. Controller delegates to an Action (business logic), often receiving typed Data objects.
  4. Action performs work (model mutations, service calls).
  5. Response returned via Inertia (server-side data → Vue page component) or JSON.
  6. Frontend renders reactive UI; subsequent interactions use Inertia navigation or useHttp.

Shop-scoped routes sit behind a {shop} prefix; MakeCurrentShop resolves the tenant and Shop::current() is available for the rest of the request (and inside queued jobs).

Frontend Page Resolution

Layout selection happens in resources/js/app.ts:

  • Pages/Auth/* and the top-level Error page → GuestLayout
  • Pages/Client/*ClientLayout
  • everything else (including Pages/Shop/*, Pages/Profile/*, Pages/Public/*) → AppLayout

Override per page with defineOptions({ layout: MyLayout }), or opt out entirely with noLayout: true.

Multi-Tenancy Strategy

Single database; the User model distinguishes contexts:

  • User::isShop() vs User::isClient() shapes authorization, route access, and UI.
  • Users belong to many Shops (ShopUser) or many Clients (ClientShop) with a "current" one held on the user.

Permissions layered via spatie/laravel-permission roles & direct permissions. See multi-tenancy.md.

Data & Type Pipeline

  • PHP DTOs (app/Data) and Enums (app/Entities) transformed by spatie/laravel-typescript-transformer & foxbytehq/laravel-backed-enums, with project-specific customizations in app/Transformers.
  • A Vite watch plugin runs composer run transform-types when app/{Data,Entities}/**/*.php changes → regenerates resources/js/types/generated.ts and resources/js/types/enumRegistry.ts.
  • A second watch plugin runs npm run generate:icons when resources/icons/**/*.svg changes.
  • DO NOT edit the generated files manually. See types.md.

laravel/scout + Meilisearch. Searchable models use the App\Traits\ShopSearchable trait so every index is shop-scoped: Client, Car, WorkOrder, Product, Service, Supplier, PurchaseOrder. Index settings live in config/scout.php. Tests force scout.driver=null.

Asynchronous & Realtime

  • Queues: Redis driver managed by Horizon, running in its own horizon container in development (with spatie/laravel-horizon-watcher restarting it on file changes).
  • WebSockets: Laravel Reverb (self-hosted Pusher-protocol server) + laravel-echo & pusher-js on the frontend.
  • Models broadcast through the BroadcastsEvents trait on App\Models\Model, on a per-instance channel (models.{class}.{id}) only — a class-wide channel would authorize via viewAny() and leak records across tenants.
  • Events & Listeners in app/Events and app/Listeners coordinate broadcast & persistent side-effects.

Webhooks

Inbound webhooks use spatie/laravel-webhook-client; routes in routes/webhook.php (CSRF-exempt via preventRequestForgery(except: ['webhook/*'])), handlers under app/Webhook. Normalization handled by app/Normalizers & ValueObjects for structured ingestion.

Error Responses

bootstrap/app.php renders 500/503/404/403 as the Inertia Error page outside local/testing, and turns 419 into a back() redirect with a "page expired" message.

Cross-Cutting Concerns

ConcernImplementation
Authenticationlaravel/fortify (+ Sanctum guard for API tokens)
AuthorizationPolicies + Spatie Permission
ValidationForm Request classes, or rules() on Actions used as controllers
Logging & AuditSpatie Activity Log (via App\Traits\LogsActivity) + standard Laravel logs
Error TrackingSentry (config in config/sentry.php)
App MetricsLaravel Pulse (config/pulse.php), Nightwatch
CachingRedis (config in config/cache.php)
SearchScout + Meilisearch (config/scout.php)
MediaSpatie Media Library (models implement HasMedia)
PDFspatie/laravel-pdf + Browsershot against the chromium container
MoneyApp\Casts\Moneybigint cents in the DB, decimal in PHP
Internationalizationvue-i18n (resources/js/Locales) + lang/ + Spatie Translatable
Recurrencerlanvin/php-rrule for repeating schedules

Extensibility Principles

  • Favor Actions over fat Services; Actions compose Services.
  • Keep models lean (relationships, scopes, attribute casting via the casts() method).
  • Use Value Objects for compound primitives.
  • Prefer explicit constructor injection; avoid service locator patterns.
  • Maintain testability by isolating side effects (queueing, external APIs) behind interfaces and facades.

Proceed to backend.md for deeper Laravel specifics.