Skip to content

Backend (Laravel 13)

Runtime Stack

  • PHP 8.4 (declare(strict_types=1); in every file)
  • Laravel Framework ^13.24
  • PostgreSQL 17 (primary relational store)
  • Redis (queue, cache, broadcasting)
  • Meilisearch (full-text search via Scout)
  • Horizon for queue processing & monitoring
  • Reverb for WebSocket broadcasting
  • Chromium (Browsershot) for PDF rendering
  • S3-compatible storage via Flysystem (MinIO locally)

Core Packages

PackagePurpose
lorisleiva/laravel-actionsEncapsulated executable classes (invoke as controller, job, command)
spatie/laravel-dataStrongly typed DTOs for request/response mapping
spatie/laravel-permissionRoles & permissions management
spatie/laravel-activitylogAuditing & activity logs
spatie/laravel-medialibraryMedia & file attachment handling
spatie/laravel-pdf + spatie/browsershotPDF generation (invoices, reports)
spatie/laravel-sluggableAutomatic slug generation
spatie/laravel-translatableTranslatable model attributes
spatie/laravel-typescript-transformerTS type generation
spatie/laravel-webhook-clientInbound webhook processing
spatie/simple-excelCSV/XLSX import/export
foxbytehq/laravel-backed-enumsEnum metadata & TS transformation
tightenco/ziggyNamed route exposure to frontend
inertiajs/inertia-laravelServer-client bridge for SPA navigation
laravel/fortifyAuthentication backend (login, registration, 2FA, password reset)
laravel/sanctumAPI token & SPA auth guard
laravel/scout + meilisearch/meilisearch-phpSearch indexing & querying
laravel/horizonQueue dashboard & advanced management
laravel/reverbReal-time broadcasting (WebSockets)
laravel/pulse (+ pulse-outdated, pulse-4xx, pulse-schedule)Application performance & health metrics
laravel/nightwatchProduction monitoring agent
laravel/aiLLM integration used by app/Ai
sentry/sentry-laravelError & performance monitoring
nunomaduro/essentialsSane framework defaults (strict models, etc.)
predis/predisRedis client library
league/flysystem-aws-s3-v3S3 storage driver
symfony/mailgun-mailerTransactional email transport
twilio/sdkSMS / communication integration
rlanvin/php-rruleRecurrence rules for schedules
staudenmeir/eloquent-has-many-deepDeep relationship traversal
pelmered/fake-carVehicle test data generation
paquettg/php-html-parserHTML parsing utilities

Exact version constraints live in composer.json — treat that file, not this table, as the source of truth.

Application Bootstrapping

  • bootstrap/app.php registers middleware, routing, and exception handling (no Kernel.php, no Exceptions/Handler.php).
  • Service providers are declared in bootstrap/providers.php.
  • The web middleware stack appends HandleUserLocale, HandleInertiaRequests, AddLinkHeadersForPreloadedAssets.
  • webhook/* is exempt from CSRF; the locale cookie is exempt from encryption.

Actions

Actions replace heavy controllers. They use the AsAction trait (not a base class) and can serve HTTP, be queued, be run from the CLI, and validate their own input.

php
declare(strict_types=1);

namespace App\Actions;

use App\Models\Car;
use App\Models\Shop;
use Illuminate\Http\RedirectResponse;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;

class CreateCar
{
    use AsAction;

    public function rules(): array
    {
        return [
            'vin' => ['required', 'string', 'size:17'],
            'year' => ['required', 'integer'],
        ];
    }

    public function authorize(ActionRequest $request): bool
    {
        return $request->user()->can('create', Car::class);
    }

    public function handle(Shop $shop, string $vin, int $year): Car
    {
        return $shop->cars()->create(compact('vin', 'year'));
    }

    public function asController(ActionRequest $request): RedirectResponse
    {
        $this->handle(Shop::current(), ...$request->validated());

        return back();
    }
}

Gotchas:

  • authorize() runs before asController(); a policy failure is a 403.
  • ::run() maps its arguments positionally to handle() — changing the handle() signature breaks every caller.

Models

All models extend App\Models\Model, which mixes in Userstamps, CascadeDeletes, HasIncrementingColumn, LogsActivity and BroadcastsEvents, and auto-casts the timestamp columns. Pivot models extend App\Models\Pivot.

Guidelines:

  • Use the casts() method instead of the $casts property.
  • Money columns are bigint cents in PostgreSQL and always cast with App\Casts\Money — never decimal/numeric.
  • Cascading deletes are declarative: protected array $cascadeDeletes = ['lines'];.
  • Avoid fat models: push logic into Actions/Services.
  • Eager-load relationships to mitigate N+1 (->with([...])).
  • When modifying a column in a migration, restate all of its previous attributes or they are silently dropped.

Search (Scout + Meilisearch)

Searchable models use App\Traits\ShopSearchable (not the bare Scout Searchable) so indexes stay shop-scoped: Client, Car, WorkOrder, Product, Service, Supplier, PurchaseOrder.

Adding search to a new model:

  1. use App\Traits\ShopSearchable;
  2. Implement toSearchableArray() — always include shop_id.
  3. Add getSearchableRelations(): array for eager loading.
  4. Add index settings in config/scout.php under meilisearch.index-settings.
  5. sail artisan scout:sync-index-settings
  6. sail artisan scout:import "App\Models\YourModel"

Validation

  • Form Requests (make:request) for incoming HTTP data.
  • Actions used as controllers validate through rules() + ActionRequest.
  • DTOs (Spatie Data) carry sanitized & typed structures further into the domain.

Authorization

  • Policies in app/Policies determine access per model or action.
  • spatie/laravel-permission attaches roles & direct permissions.
  • Prefer Gate::allows(), $this->authorize(), or an Action's authorize() method.

Events & Broadcasting

  • Domain events in app/Events.
  • Listeners in app/Listeners handle side-effects (queue work, logs, notifications).
  • Broadcasting channels defined in routes/channels.php.
  • Model broadcasting goes out on the per-instance channel models.{morphClass}.{id} and never to the originating user.

Webhooks

  • Endpoints in routes/webhook.php.
  • Handlers leverage spatie/laravel-webhook-client signature validation and process payloads into ValueObjects via app/Normalizers.

PDF & Media

  • Invoices & reports produced using spatie/laravel-pdf on top of Browsershot, pointed at the chromium container (see config/browser-shot.php).
  • Media attachments saved through Media Library (model implements HasMedia and uses InteractsWithMedia).

Error Handling

  • Exception behavior is configured in bootstrap/app.php: Sentry integration, an Inertia Error page for 403/404/500/503 outside local/testing, and a friendly redirect for 419.

Configuration Overview

Important config files:

  • config/inertia.php – Inertia root view & SSR settings.
  • config/horizon.php / config/horizon-watcher.php – queue balancing, dashboard, dev auto-restart paths.
  • config/reverb.php – WebSocket server settings.
  • config/scout.php – Meilisearch connection & per-index settings.
  • config/permission.php – role/permission caching.
  • config/sentry.php – DSN & sample rates.
  • config/pulse.php – metric recorders & dashboard access.
  • config/typescript-transformer.php – type generation selectors.
  • config/webhook-client.php – signing secrets & profile definitions.
  • config/browser-shot.php – Chromium endpoint for PDF rendering.
  • config/ai.php – model & provider settings for app/Ai.

CLI & Scripts

  • composer run transform-types – runs typescript:transform then generate:enum-registry.
  • composer run dev – host-only convenience script (serve + queue:listen + pail + vite). With Sail the app and Horizon already run in containers, so npm run dev alone is normally enough.

Performance Tips

  • Use indexing & constraints in migrations explicitly.
  • Avoid loading large media collections without pagination.
  • Cache permission lookups via Spatie's internal cache (clear on role/permission changes).
  • Offload heavy tasks to queues (PDF generation, external API calls, webhook processing, search indexing).

See frontend.md for SPA layer details.