Appearance
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
| Package | Purpose |
|---|---|
lorisleiva/laravel-actions | Encapsulated executable classes (invoke as controller, job, command) |
spatie/laravel-data | Strongly typed DTOs for request/response mapping |
spatie/laravel-permission | Roles & permissions management |
spatie/laravel-activitylog | Auditing & activity logs |
spatie/laravel-medialibrary | Media & file attachment handling |
spatie/laravel-pdf + spatie/browsershot | PDF generation (invoices, reports) |
spatie/laravel-sluggable | Automatic slug generation |
spatie/laravel-translatable | Translatable model attributes |
spatie/laravel-typescript-transformer | TS type generation |
spatie/laravel-webhook-client | Inbound webhook processing |
spatie/simple-excel | CSV/XLSX import/export |
foxbytehq/laravel-backed-enums | Enum metadata & TS transformation |
tightenco/ziggy | Named route exposure to frontend |
inertiajs/inertia-laravel | Server-client bridge for SPA navigation |
laravel/fortify | Authentication backend (login, registration, 2FA, password reset) |
laravel/sanctum | API token & SPA auth guard |
laravel/scout + meilisearch/meilisearch-php | Search indexing & querying |
laravel/horizon | Queue dashboard & advanced management |
laravel/reverb | Real-time broadcasting (WebSockets) |
laravel/pulse (+ pulse-outdated, pulse-4xx, pulse-schedule) | Application performance & health metrics |
laravel/nightwatch | Production monitoring agent |
laravel/ai | LLM integration used by app/Ai |
sentry/sentry-laravel | Error & performance monitoring |
nunomaduro/essentials | Sane framework defaults (strict models, etc.) |
predis/predis | Redis client library |
league/flysystem-aws-s3-v3 | S3 storage driver |
symfony/mailgun-mailer | Transactional email transport |
twilio/sdk | SMS / communication integration |
rlanvin/php-rrule | Recurrence rules for schedules |
staudenmeir/eloquent-has-many-deep | Deep relationship traversal |
pelmered/fake-car | Vehicle test data generation |
paquettg/php-html-parser | HTML parsing utilities |
Exact version constraints live in composer.json — treat that file, not this table, as the source of truth.
Application Bootstrapping
bootstrap/app.phpregisters middleware, routing, and exception handling (noKernel.php, noExceptions/Handler.php).- Service providers are declared in
bootstrap/providers.php. - The web middleware stack appends
HandleUserLocale,HandleInertiaRequests,AddLinkHeadersForPreloadedAssets. webhook/*is exempt from CSRF; thelocalecookie 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 beforeasController(); a policy failure is a 403.::run()maps its arguments positionally tohandle()— changing thehandle()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$castsproperty. - Money columns are
bigintcents in PostgreSQL and always cast withApp\Casts\Money— neverdecimal/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:
use App\Traits\ShopSearchable;- Implement
toSearchableArray()— always includeshop_id. - Add
getSearchableRelations(): arrayfor eager loading. - Add index settings in
config/scout.phpundermeilisearch.index-settings. sail artisan scout:sync-index-settingssail 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/Policiesdetermine access per model or action. spatie/laravel-permissionattaches roles & direct permissions.- Prefer
Gate::allows(),$this->authorize(), or an Action'sauthorize()method.
Events & Broadcasting
- Domain events in
app/Events. - Listeners in
app/Listenershandle 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-clientsignature validation and process payloads into ValueObjects viaapp/Normalizers.
PDF & Media
- Invoices & reports produced using
spatie/laravel-pdfon top of Browsershot, pointed at thechromiumcontainer (seeconfig/browser-shot.php). - Media attachments saved through Media Library (model implements
HasMediaand usesInteractsWithMedia).
Error Handling
- Exception behavior is configured in
bootstrap/app.php: Sentry integration, an InertiaErrorpage for 403/404/500/503 outsidelocal/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 forapp/Ai.
CLI & Scripts
composer run transform-types– runstypescript:transformthengenerate:enum-registry.composer run dev– host-only convenience script (serve +queue:listen+ pail + vite). With Sail the app and Horizon already run in containers, sonpm run devalone 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.