Version: 2.3.0
Laravel Vision is a modular Laravel 12 API template built for multi-tenant SaaS projects. The repository is intentionally opinionated: every business area lives in its own domain, every new project should learn the structure from the 4 default domains already present in src/Domains, and every next domain should stay identical at the folder level.
The template currently ships with 4 reference domains:
Administration- users, company, roles, permissions.Auth- login, logout, refresh token, social login, auth logs.Payment- services, proformas, invoices, billing.FileManager- directories, files, metadata, downloads, uploads.
src/Shared is not a domain. It contains cross-cutting building blocks reused by multiple domains.
Every domain in src/Domains/{Domain} must contain exactly the same 10 top-level folders, even when some of them are empty. Empty layers should stay in git via .gitkeep.
This is a hard template rule. New projects should treat the existing 4 domains as the source of truth and keep every newly created domain structurally identical.
src/Domains/{Domain}/
+-- Dtos/
+-- Enums/
+-- Events/
+-- Factories/
+-- Models/
+-- Observers/
+-- Repositories/
+-- Requests/
+-- Resources/
+-- Services/
| Folder | Responsibility |
|---|---|
Dtos |
Immutable input/output objects passed between requests, controllers and services. |
Enums |
Domain enums and strongly typed value sets. |
Events |
Domain events and event-related classes. Event listeners should live under Events/Listeners/, not as an extra top-level folder. |
Factories |
Domain-specific factories and object builders. |
Models |
Eloquent models and relationships. |
Observers |
Model observers and persistence rules that should not live in controllers. |
Repositories |
Data access layer. Concrete repositories live here, and repository interfaces live in Repositories/Interfaces/. |
Requests |
Domain FormRequest classes and request-to-DTO mapping. |
Resources |
JSON/API resources used for response serialization. |
Services |
Application/business logic. Service interfaces live in Services/Interfaces/. |
The project now keeps interface placement symmetrical:
- Service interfaces:
src/Domains/{Domain}/Services/Interfaces/* - Repository interfaces:
src/Domains/{Domain}/Repositories/Interfaces/*
Concrete classes remain one level above:
Services/{Something}Service.phpRepositories/{Something}Repository.php
All bindings are registered centrally in RegisterServiceProvider.
src/
+-- App/
| +-- Http/Controllers/ # single-action controllers
| +-- Providers/ # route, event and DI registration
+-- Domains/
| +-- Administration/
| +-- Auth/
| +-- FileManager/
| +-- Payment/
+-- Shared/ # traits, scopes, exceptions, helpers, middleware
The preferred flow is consistent across the template:
Route
-> Middleware
-> FormRequest
-> Controller (__invoke only)
-> Service
-> Repository
-> Model / Observer
-> Event / Resource / JSON response
- Controllers stay thin and coordinate only the HTTP layer.
- Requests validate input and build DTOs.
- Services contain business logic.
- Repositories contain data access logic.
- Observers enforce model lifecycle rules.
- Events broadcast or notify about domain changes.
- Shared concerns belong in
src/Shared, not inside random domains.
The project uses two main route entry points:
- routes/oauth.php for public authentication endpoints.
- routes/api.php for protected business endpoints.
Protected API groups currently cover:
manage/*administration/*files/*management/*
Implemented stack in code:
- Laravel Passport for OAuth2 token handling.
- Laravel Socialite for social login providers.
- Spatie Laravel Permission in teams mode with
company_id. - Middleware-based permission checks in routes.
- Request-level authorization inside
FormRequestclasses.
Permissions are grouped by modules in config/permission.php. Some modules are already placeholders for future projects, which is expected in this template.
The template is designed around company-based multi-tenancy:
company_idis the permission team key.- shared traits/scopes enforce company-aware querying.
- routes use
teams.permissionandcompany.activemiddleware where needed. - new domains should reuse the same tenant conventions instead of inventing their own.
When adding a new domain:
- Copy the folder contract from one of the 4 existing default domains.
- Create all 10 top-level folders immediately, even if some are empty.
- Keep interfaces in
Services/InterfacesandRepositories/Interfaces. - Register bindings in RegisterServiceProvider.
- Register events/listeners in EventServiceProvider when needed.
- Expose endpoints through the existing routing style in routes/api.php or routes/oauth.php.
- Reuse
src/Sharedfor cross-domain concerns instead of duplicating helpers.
The usual resource flow is:
App/Http/Controllers/{ResourcePlural}/
Domain/Requests/
Domain/Dtos/
Domain/Services/Interfaces/
Domain/Services/
Domain/Repositories/Interfaces/
Domain/Repositories/
Domain/Models/
Domain/Observers/
Domain/Events/
Domain/Resources/
Not every resource needs every artifact, but the domain itself must always preserve the 10-folder contract.
- user management
- roles and permissions
- company-related administration concerns
- login
- logout
- refresh token
- social auth redirect/callback
- auth log handling
- services catalog
- proformas
- invoices
- billing
- directory listing
- directory creation
- file upload/download
- metadata loading
- path updates and deletion
Verified from the repository:
- PHP
^8.4 - Laravel
^12.0 - Laravel Passport
^13.2.2 - Laravel Reverb
1.7.0.0 - Laravel Socialite
^5.25 - Spatie Laravel Permission
^6.21 - Redis/Predis support
- PHPUnit
^11.5.3 - Vite + Tailwind CSS 4
composer install
php artisan key:generate
php artisan migrate
composer test
npm install
npm run devTests are split into:
tests/Unittests/Featuretests/Requests
The suite already contains coverage for auth, administration, management, file manager, DTOs, enums and selected model/exception behavior.