laravel-ddd-modular maintained by aurnob
Laravel DDD Modular
aurnob/laravel-ddd-modular is an opinionated modular architecture package for Laravel applications that want clear boundaries, DDD-style structure, predictable scaffolding, and optional ecosystem integrations without turning the package into a thin wrapper around another package.
It is standalone first, but it can live alongside nwidart/laravel-modules because it uses the same broad Modules/ convention and does not require a hard dependency on that package.
Table of Contents
- Why This Package Exists
- Package Goals
- Requirements and Compatibility
- Installation
- Quick Start
- Generated Module Structure
- How Module Discovery Works
- The module.json Manifest
- Artisan Command
- Module Features
- Optional Integrations
- Configuration
- Runtime Behavior
- Extending the Package
- Working Alongside nwidart/laravel-modules
- Testing the Package
Why This Package Exists
Most Laravel module packages solve discovery and folder generation, but they usually stop at generic folders. Real applications often need stronger conventions:
- domain models and query builders
- application actions and DTOs
- presentation layer controllers, requests, resources, and view models
- clear infrastructure folders
- optional API-first scaffolding
- predictable support for common Laravel ecosystem packages
This package provides those conventions directly.
Package Goals
- Keep the core small and readable
- Make modules feel first-class, not bolted on
- Prefer composition and contracts over inheritance-heavy design
- Support Laravel 10 through 13 with broad PHP compatibility
- Support API-first teams, especially mobile and SPA backends using Sanctum cookie-based authentication
- Make future package features pluggable instead of hard-coded
Requirements and Compatibility
PHP
^8.2|^8.3|^8.4|^8.5
Primary runtime target:
- PHP 8.5
Laravel Components
illuminate/support: ^10.0|^11.0|^12.0|^13.0illuminate/console: ^10.0|^11.0|^12.0|^13.0illuminate/filesystem: ^10.0|^11.0|^12.0|^13.0
Official Laravel Documentation Verified Before Choosing Constraints
- Laravel package development and auto-discovery:
laravel.com/docs/13.x/packages - Laravel 13 upgrade path requiring
laravel/framework^13.0:laravel.com/docs/13.x/upgrade - Release compatibility matrix across Laravel 10, 11, 12, and 13:
laravel.com/docs/12.x/releases
The package keeps compatibility broad unless a narrower constraint becomes technically necessary.
Installation
Install the package:
composer require aurnob/laravel-ddd-modular
Publish the configuration:
php artisan vendor:publish --tag=modular-config
Publish the stubs if you want to customize generated files:
php artisan vendor:publish --tag=modular-stubs
Laravel package auto-discovery is already configured through:
{
"extra": {
"laravel": {
"providers": [
"Aurnob\\LaravelDddModular\\LaravelDddModularServiceProvider"
]
}
}
}
Quick Start
Generate a basic module:
php artisan modular:make Blog
Generate an API-focused module with testing, policies, observers, and events:
php artisan modular:make Catalog \
--feature=api \
--feature=testing \
--feature=policies \
--feature=observers \
--feature=events
Generate a module while disabling a configured default feature:
php artisan modular:make Billing --without-feature=testing
Generated Module Structure
The default generated structure is:
Modules/
└── Blog/
├── module.json
├── Domain/
│ ├── Models/
│ └── QueryBuilders/
├── Application/
│ ├── Actions/
│ └── Data/
├── Infrastructure/
│ ├── Migrations/
│ ├── Providers/
│ └── Repositories/
├── Presentation/
│ ├── Http/
│ │ ├── Controllers/
│ │ ├── Data/
│ │ ├── Requests/
│ │ └── Resources/
│ ├── ViewModels/
│ └── Views/
├── config/
├── lang/
└── routes/
The default example files include:
module.jsonInfrastructure/Providers/<Module>ServiceProvider.phpDomain/Models/<Module>.phpApplication/Actions/Create<Module>Action.phpApplication/Data/<Module>Data.phpPresentation/Http/Controllers/<Module>Controller.phpPresentation/Http/Requests/Store<Module>Request.phpPresentation/Http/Resources/<Module>Resource.phpPresentation/ViewModels/Show<Module>ViewModel.phproutes/web.phpPresentation/Views/index.blade.phpconfig/module.php- an example migration
If Astrotomic Translatable integration is active, model and migration generation switch to translatable equivalents.
How Module Discovery Works
At runtime the package:
- Scans the configured modules directory
- Looks for module folders containing
module.json - Reads the module manifest
- Registers only enabled modules
- Merges module config
- Loads module resources
- Registers module service providers
Discovery is handled by the module finder and registry, not by generated module providers themselves.
The module.json Manifest
Each module is described by module.json.
Example:
{
"name": "Catalog",
"slug": "catalog",
"description": "Catalog module.",
"enabled": true,
"namespace": "Modules\\Catalog",
"features": [
"api",
"testing",
"policies"
],
"providers": [
"Modules\\Catalog\\Infrastructure\\Providers\\CatalogServiceProvider"
],
"paths": {
"routes": "routes",
"views": "Presentation/Views",
"migrations": "Infrastructure/Migrations",
"translations": "lang",
"config": "config"
},
"feature_manifest": {
"api": {
"guard": "sanctum",
"middleware": [
"api",
"auth:sanctum"
]
}
},
"priority": 0
}
Important Manifest Keys
name: human-friendly module nameslug: route/config friendly identifierenabled: whether the module should be bootednamespace: base PHP namespace for the modulefeatures: feature keys used when the module was generatedproviders: module service providers to registerpaths: relative paths for routes, views, migrations, translations, configfeature_manifest: extra metadata generated by featurespriority: load ordering hint
Artisan Command
Basic Syntax
php artisan modular:make {name}
Available Options
php artisan modular:make Blog \
--force \
--feature=api \
--feature=events \
--feature=testing \
--without-feature=testing
Supported Feature Keys
apipermissionsmediaeventsjobsobserverspoliciestesting
If an unknown feature is requested, the command fails with a clear error listing valid feature keys.
Module Features
Features are the package’s future-proof extension point. They let scaffolding grow without turning ModuleGenerator into a god class.
Each feature can contribute:
- directories
- generated files
- manifest metadata
- service provider imports
- service provider boot logic
- service provider register logic
- action imports
- action post-create hooks
api
Purpose:
- generate API routes and an API controller
- default to Sanctum-protected API middleware
- support mobile clients and SPA backends
Generated files:
Presentation/Http/Controllers/Api/<Module>ApiController.phproutes/api.php
Default API middleware:
['api', 'auth:sanctum']
Default API URI prefix:
api/<module-slug>
permissions
Purpose:
- create a single place for module permission names
- prepare future integration with permission packages or Gate rules
Generated file:
Domain/Permissions/<Module>Permissions.php
media
Purpose:
- establish media collection conventions without hard-coding a media library
- prepare future integration with media packages
Generated files:
Domain/Media/<Module>Media.phpApplication/Actions/Attach<Module>MediaAction.php
events
Purpose:
- generate domain event scaffolding
- register listeners in the module provider
- dispatch an event from the generated create action
Generated files:
Domain/Events/<Module>Created.phpApplication/Listeners/Update<Module>Projection.php
Provider behavior:
- registers the listener with
Event::listen(...)
Action behavior:
- dispatches
event(new <Module>Created($model))
jobs
Purpose:
- create queue job presets inside the application layer
Generated file:
Application/Jobs/Sync<Module>SearchIndexJob.php
observers
Purpose:
- add model observer scaffolding and provider registration
Generated file:
Infrastructure/Observers/<Module>Observer.php
Provider behavior:
- registers
<Module>::observe(<Module>Observer::class)
policies
Purpose:
- create authorization policy scaffolding
- register policy mappings in the module provider
Generated file:
Application/Policies/<Module>Policy.php
Provider behavior:
- registers
Gate::policy(...)
testing
Purpose:
- create example module-level test folders and starter tests
Generated files:
tests/Feature/<Module>ApiTest.phportests/Feature/<Module>WebTest.phptests/Unit/Create<Module>ActionTest.php
The generated test files intentionally use markTestSkipped(...) because every application’s test bootstrapping strategy is different. They are presets, not false-positive tests.
Optional Integrations
The package supports several optional ecosystem integrations in a package-detection-driven way.
Spatie Laravel Data
Package:
spatie/laravel-data
Behavior:
- generated DTOs extend
Spatie\LaravelData\Data
Fallback if not installed:
- generates a native readonly DTO with
from()andtoArray()
Spatie Laravel View Models
Package:
spatie/laravel-view-models
Behavior:
- generated view models extend
Spatie\ViewModels\ViewModel
Fallback if not installed:
- generates an
Arrayableview model that still works withview()
Astrotomic Translatable
Package:
astrotomic/laravel-translatable
Behavior:
- generated model uses
Translatable - translation model is generated
- migrations split into aggregate and translation tables
Laravel Localization
Package:
mcamara/laravel-localization
Behavior:
- if enabled in config and route localization is turned on, discovered module routes are wrapped in a localized group
Configuration
The published config file is config/modular.php.
Modules
'modules' => [
'path' => base_path('Modules'),
'namespace' => 'Modules',
'manifest' => 'module.json',
'default_folders' => [
'Domain/Models',
'Domain/QueryBuilders',
'Application/Actions',
'Application/Data',
'Infrastructure/Migrations',
'Infrastructure/Repositories',
'Infrastructure/Providers',
'Presentation/Http/Controllers',
'Presentation/Http/Requests',
'Presentation/Http/Data',
'Presentation/Http/Resources',
'Presentation/ViewModels',
'Presentation/Views',
'routes',
'config',
'lang',
],
],
Use this section to control:
- where modules live
- their base namespace
- the manifest filename
- which folders are always generated
Discovery
'discovery' => [
'scan_depth' => 1,
'require_manifest' => true,
],
Use this section to control:
- how deep the scanner searches
- whether a module must contain
module.json
Routes
'routes' => [
'enabled' => true,
'path' => 'routes',
'loading' => 'directory',
'localized' => false,
],
Migrations
'migrations' => [
'enabled' => true,
'path' => 'Infrastructure/Migrations',
'loading' => 'directory',
],
Views
'views' => [
'enabled' => true,
'path' => 'Presentation/Views',
'namespace_strategy' => 'slug',
'namespace_prefix' => null,
],
Supported namespace strategies:
slugstudlyprefix
Translations
'translations' => [
'enabled' => true,
'path' => 'lang',
],
Module Config Loading
'config_loading' => [
'enabled' => true,
'path' => 'config',
'key_prefix' => 'modular.modules',
],
Module config files are merged under keys like:
config('modular.modules.blog.module');
Stub Overrides
'stubs' => [
'path' => null,
],
If you publish or maintain custom stubs, point this path to your own stub directory.
Feature Defaults and Feature Config
'features' => [
'defaults' => [],
'available' => [
'api',
'permissions',
'media',
'events',
'jobs',
'observers',
'policies',
'testing',
],
'api' => [
'routes_path' => 'routes/api.php',
'route_name_prefix' => 'api.',
'uri_prefix' => 'api',
'middleware' => [
'api',
'auth:sanctum',
],
],
'testing' => [
'path' => 'tests',
],
],
Recommended API-first team setup:
'features' => [
'defaults' => ['api', 'testing'],
],
Integrations
'integrations' => [
'spatie_data' => true,
'spatie_view_models' => true,
'astrotomic_translatable' => false,
'laravel_localization' => false,
'laravel_localization_middleware' => [
'localize',
'localizationRedirect',
'localeViewPath',
],
],
Runtime Behavior
When the application boots, enabled modules are processed as follows:
- module namespaces are registered with a runtime autoloader
- module config files are merged
- module service providers are registered
- module views are loaded
- module translations are loaded
- module migrations are loaded
- module route files are required
This keeps modules self-contained while the package owns the cross-cutting runtime behavior.
Extending the Package
The package is designed so future capabilities can plug in cleanly.
Add a New Feature
Implement:
Aurnob\LaravelDddModular\Contracts\ModuleFeature
Return a ModuleFeatureContribution that can provide:
- directories
- stub-driven files
- manifest metadata
- provider register statements
- provider boot statements
- action imports
- action post-create statements
Then register the feature in the package service provider or your own application-level extension layer.
Why Features Use Composition
Do not extend ModuleGenerator or ModuleRegistrar for every new capability. That quickly creates a maintenance problem.
Instead:
- feature classes contribute isolated pieces
- the feature manager resolves and coordinates them
- the generator merges the contributions into the final module output
That is the main extensibility model of the package.
Working Alongside nwidart/laravel-modules
This package does not require nwidart/laravel-modules, but it can coexist with it if you already use its Modules/ directory convention.
Recommended approach:
- let this package own the opinionated structure and scaffolding
- keep
module.jsonas the source of truth for this package - avoid mixing multiple independent discovery systems for the same resources unless you understand the boot order clearly
Testing the Package
This package currently includes tests for:
- module discovery
- registry lookup
- enabled versus disabled modules
- config merge behavior
- view namespace behavior
- base command generation
- feature-driven command generation
- cross-version fixture validation against Laravel 10, 11, 12, and 13
To run tests locally after installing dependencies:
composer test
To rerun the real Laravel fixture matrix used during compatibility verification:
scripts/run-fixture-matrix.sh host
If Docker is available and the fixture workspace exists at ../laravel-ddd-modular-fixtures, you can run the Docker matrix too:
scripts/run-fixture-matrix.sh docker
The committed runner expects the fixture applications to live in /home/aurnob/www/laravel-ddd-modular-fixtures by default, but you can override that path with FIXTURE_ROOT=/custom/path.
Summary
Use this package if you want:
- strong module conventions
- DDD-oriented folders by default
- feature-based scaffolding
- optional API-first generation
- optional ecosystem integrations
- an architecture that stays maintainable as new module capabilities are added