Domain-Based Laravel Project Structure
In this article you will learn about, Domain-Based Laravel Project Structure.
As Laravel projects grow, maintaining a clear structure becomes critical. Traditional organization (controllers and models in separate folders) can lead to spaghetti code in complex systems. In this guide, we’ll explore domain-based organization—a professional strategy adopted by companies like Spotify and Amazon to scale applications successfully.
What Is Domain-Based Organization?
Domain-based organization groups all artifacts of a feature (controllers, models, services, views) into a single folder. Imagine an e-commerce platform:
- “Orders” Domain: Contains
OrderController
,Order
,OrderService
, policies, and order migrations. - “Users” Domain: Includes
UserProfileController
,Subscription
, and authentication services.
Comparison with Traditional Structure
Traditional | Domain-Based |
---|---|
app/Http/Controllers/* |
app/Domains/Orders/Controllers/ |
app/Models/* |
app/Domains/Orders/Models/ |
resources/views/* |
app/Domains/Orders/Views/ |
Key Advantage: Reduces cognitive distance between related files, speeding up team development.
Benefits for Growing Projects
1. Intuitive Navigation
Instead of jumping between app/Models
, app/Services
, and app/Http/Controllers
, developers focus on one folder per feature. Example:
app/Domains/Products/
:Controllers/ProductController.php
Models/Product.php
Services/InventoryService.php
Views/products/*.blade.php
2. Context Isolation
Each domain acts as an independent module, enabling:
- Focused unit tests (e.g.,
tests/Domains/Orders/OrderTest.php
). - Reusability in other projects (e.g., a payment module as an internal package).
- Clear task delegation across teams (e.g., a “Checkout” squad managing the
Orders
domain).
3. Evolution Without Coupling
Adding features like “discount coupons” doesn’t require modifying global folders—just create app/Domains/Discounts/
.
Step-by-Step Implementation in Laravel 11
Step 1: Creating the Folder Structure
Generate domain folders via the terminal:
mkdir -p app/Domains/{Orders,Users,Products}/{Controllers,Models,Services,Views}
Step 2: Moving Existing Files
Restructure incrementally:
// Old: app/Models/Order.php
// New: app/Domains/Orders/Models/Order.php
namespace App\Domains\Orders\Models;
use Illuminate\Database\Eloquent\Model;
class Order extends Model { /* ... */ }
Step 3: Configuring Namespaces
Update composer.json
to autoload domains:
"autoload": {
"psr-4": {
"App\\": "app/",
"App\\Domains\\": "app/Domains/"
}
}
Run:
composer dump-autoload
Step 4: Organizing Routes
Group routes by domain in routes/web.php
:
Route::group(['namespace' => 'App\Domains\Orders\Controllers'], function () {
Route::get('/orders', 'OrderController@index');
Route::post('/orders', 'OrderController@store');
});
Advanced Alternative: Create separate route files like routes/orders.php
and register them via RouteServiceProvider
.
Practical Example: “Subscriptions” Domain Structure
app/ └── Domains/ └── Subscriptions/ ├── Controllers/ │ ├── SubscriptionController.php │ └── InvoiceController.php ├── Models/ │ ├── Subscription.php │ └── Payment.php ├── Services/ │ ├── BillingService.php │ └── NotificationService.php ├── Views/ │ └── subscriptions/ │ ├── index.blade.php │ └── invoice.blade.php └── Policies/ └── SubscriptionPolicy.php
When (and When Not) to Use This Approach
Ideal Cases:
- Enterprise Apps: ERPs, marketplaces, or SaaS platforms with 50+ models.
- Large Teams: Squads working on parallel features without conflicts.
- Long-Term Projects: Where maintainability is a priority.
Avoid When:
- Rapid Prototypes: MVP apps with fewer than 10 models.
- Small Teams: Lacking resources for restructuring.
- Third-Party Packages: External libraries typically follow the default structure.
Common Challenges and Solutions
Issue 1: Service Providers Can’t Find Classes
Solution: Register domain-specific providers:
// AppServiceProvider.php
public function register() {
$this->app->register(\App\Domains\Orders\Providers\OrderServiceProvider::class);
}
Issue 2: Disorganized Migrations
Solution: Create subfolders and update composer.json
:
"autoload": {
"classmap": [
"database/migrations/orders/",
"database/migrations/users/"
]
}
Tools to Simplify the Process
- Laravel Modules: Package for creating isolated domain modules.
- IDE Helper: Generates autocompletion for custom namespaces.
- PHPStan: Detects namespace errors post-restructuring.
Success Metrics
After implementation, monitor:
- Onboarding Time: New developers should understand features 30% faster.
- Coupling: Less than 5% of classes should depend on other domains.
- Build Times: Unit tests run faster due to isolation.
Conclusion: Planning for the Future
Domain-based organization is an upfront investment that pays off in long-term projects. Start with critical modules (e.g., payments) and expand gradually. Remember:
- Document: Keep a
README.md
in each domain explaining responsibilities. - Automate: Use scripts to move files and update namespaces.
- Validate: Conduct technical retrospectives to adjust the structure.
For small projects, Laravel’s default structure remains valid. The key is choosing the right approach for your system’s current stage.
Thanks for reading, Domain-Based Laravel Project Structure.