<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: saad mouizina</title>
    <description>The latest articles on DEV Community by saad mouizina (@pixelkk).</description>
    <link>https://dev.to/pixelkk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2983098%2Fdd100c15-39ac-4e87-b08e-49e4e31fe03c.png</url>
      <title>DEV Community: saad mouizina</title>
      <link>https://dev.to/pixelkk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pixelkk"/>
    <language>en</language>
    <item>
      <title>[PHP] Understanding Ports &amp; Adapters</title>
      <dc:creator>saad mouizina</dc:creator>
      <pubDate>Sun, 29 Jun 2025 13:57:46 +0000</pubDate>
      <link>https://dev.to/pixelkk/php-understanding-ports-adapters-49g0</link>
      <guid>https://dev.to/pixelkk/php-understanding-ports-adapters-49g0</guid>
      <description>&lt;p&gt;Ports and Adapters is a well-known and widely used design pattern — often without even realizing it. Foundational to maintainable and testable applications, this pattern separates the &lt;em&gt;what&lt;/em&gt; (your core logic) from the &lt;em&gt;how&lt;/em&gt; (external systems or tools). It’s commonly embedded in frameworks like Symfony and Laravel through features like dependency injection, contracts, and service containers.&lt;/p&gt;

&lt;p&gt;In this article, we’ll focus specifically on the Ports and Adapters concept, &lt;em&gt;without diving into full Hexagonal architecture&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Are Ports &amp;amp; Adapters?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ports&lt;/strong&gt; are PHP interfaces that define the actions your application expects (inputs) or provides (outputs). Think of them as your contracts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adapters&lt;/strong&gt; are concrete implementations of those interfaces. They allow communication with external systems: databases, APIs, file storage, queues, etc.&lt;/p&gt;

&lt;p&gt;By depending on interfaces rather than implementations, your core application becomes easier to test, change, and extend. For instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can mock external services in tests.&lt;/li&gt;
&lt;li&gt;You can change how data is stored without touching business logic.&lt;/li&gt;
&lt;li&gt;You can add new integrations just by writing a new adapter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how frameworks like Laravel let you swap out mail drivers, queue backends, or filesystem disks with ease.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Use Case: Configurable Data Importer&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Let’s walk through a typical example to see Ports &amp;amp; Adapters in action: a system that &lt;strong&gt;imports user data from multiple sources&lt;/strong&gt; (CSV, HTTP API) and &lt;strong&gt;stores them in multiple formats&lt;/strong&gt; (MySQL, JSONL files).&lt;/p&gt;

&lt;p&gt;We want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add new sources/targets without modifying the core logic&lt;/li&gt;
&lt;li&gt;Drive the behavior through config&lt;/li&gt;
&lt;li&gt;Keep the application decoupled from external systems&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Define Contracts (Ports)
&lt;/h3&gt;

&lt;p&gt;Let’s define our contracts :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface ImportSourceInterface
{
    /** @return Collection&amp;lt;ImportDto&amp;gt; */
    public function fetch(): Collection;
}

interface ImportTargetInterface
{
    /** @param Collection&amp;lt;ImportDto&amp;gt; $records */
    public function store(Collection $records): void;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ImportDto is a simple data carrier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ImportDto
{
    public function __construct(
        public readonly string $email,
        public readonly string $name,
    ) {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Create Adapters
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Adapters\Sources;

use App\Contracts\ImportSourceInterface;
use App\DTO\ImportDto;
use Illuminate\Support\Collection;

class CsvFileSource implements ImportSourceInterface
{
    public function __construct(private string $path) {}

    /**
     * @return Collection&amp;lt;ImportDto&amp;gt;
     */
    public function fetch(): Collection
    {
        $rows = collect();

        if (! file_exists($this-&amp;gt;path)) {
            throw new \RuntimeException("CSV file not found at {$this-&amp;gt;path}");
        }

        $handle = fopen($this-&amp;gt;path, 'r');
        $headers = fgetcsv($handle);

        while ($line = fgetcsv($handle)) {
            $row = array_combine($headers, $line);
            $rows-&amp;gt;push(new ImportDto(
                email: $row['email'] ?? '',
                name: $row['name'] ?? '',
            ));
        }

        fclose($handle);

        return $rows;
    }
}

&amp;lt;?php

namespace App\Adapters\Sources;

use App\Contracts\ImportSourceInterface;
use App\DTO\ImportDto;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;

class HttpClientSource implements ImportSourceInterface
{
    public function __construct(private string $url, private ?string $token = null) {}

    /**
     * @return Collection&amp;lt;ImportDto&amp;gt;
     */
    public function fetch(): Collection
    {
        $response = Http::withToken($this-&amp;gt;token)-&amp;gt;get($this-&amp;gt;url);

        if (! $response-&amp;gt;successful()) {
            throw new \RuntimeException('Unable to fetch data from HTTP source.');
        }

        return collect($response-&amp;gt;json())
            -&amp;gt;map(fn (array $item) =&amp;gt; new ImportDto(
                email: $item['email'] ?? '',
                name: $item['name'] ?? '',
            ));
    }
}

&amp;lt;?php

namespace App\Adapters\Targets;

use App\Contracts\ImportTargetInterface;
use App\DTO\ImportDto;
use App\Models\ImportedUser;
use Illuminate\Support\Collection;

class EloquentStorage implements ImportTargetInterface
{
    public function store(Collection $records): void
    {
        $users = $records-&amp;gt;map(function (ImportDto $record) {
            return [
                'email' =&amp;gt; $record-&amp;gt;email,
                'name' =&amp;gt; $record-&amp;gt;name,
            ];
        });

        ImportedUser::insert($users-&amp;gt;toArray());
    }
}

&amp;lt;?php

namespace App\Adapters\Targets;

use App\Contracts\ImportTargetInterface;
use Illuminate\Support\Collection;

class FileStorage implements ImportTargetInterface
{
    public function __construct(
        private string $path,
    ) {}

    public function store(Collection $records): void
    {
        file_put_contents(
            filename: $this-&amp;gt;path,
            data: json_encode($records)
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each adapter only cares about its specific task. They don’t know where they’re called from or why. That’s the job of our orchestration layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Config-Driven Behavior:
&lt;/h3&gt;

&lt;p&gt;We use a single port-adapters.php config file to list available sources, targets, and define the jobs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

return [
    // 👇 List of available sources
    'sources' =&amp;gt; [
        'http_users' =&amp;gt; [
            'class' =&amp;gt; \App\Adapters\Sources\HttpClientSource::class,
            'arguments' =&amp;gt; [
                'url' =&amp;gt; env('USERS_API_URL'),
                'token' =&amp;gt; env('USERS_API_TOKEN'),
            ],
        ],
        'local_csv' =&amp;gt; [
            'class' =&amp;gt; \App\Adapters\Sources\CsvFileSource::class,
            'arguments' =&amp;gt; [
                'path' =&amp;gt; storage_path('imports/users.csv'),
            ],
        ],
    ],

    // 👇 List of available targets
    'targets' =&amp;gt; [
        'mysql' =&amp;gt; [
            'class' =&amp;gt; \App\Adapters\Targets\EloquentStorage::class,
            'arguments' =&amp;gt; [],
        ],
        'file' =&amp;gt; [
            'class' =&amp;gt; \App\Adapters\Targets\FileStorage::class,
            'arguments' =&amp;gt; [
                'path' =&amp;gt; storage_path('imports/output/users.jsonl'),
            ],
        ],
    ],

    // 👇 Import job definitions (source ↔ target)
    'jobs' =&amp;gt; [
        'users_from_http' =&amp;gt; [
            'source' =&amp;gt; 'http_users',
            'target' =&amp;gt; 'file',
        ],
        'users_from_csv' =&amp;gt; [
            'source' =&amp;gt; 'local_csv',
            'target' =&amp;gt; 'mysql',
        ],
    ],

];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows defining import jobs like users_from_http or users_from_csv dynamically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Job Runner Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Services;

use App\Contracts\ImportSourceInterface;
use App\Contracts\ImportTargetInterface;

class ImportJobRunner
{
    public function __construct(private string $jobName) {}

    public function run(): void
    {
        $job = config("import_jobs.{$this-&amp;gt;jobName}");
        $sourceConfig = config("import_sources.{$job['source']}");
        $targetConfig = config("import_targets.{$job['target']}");

        $source = $this-&amp;gt;makeAdapter($sourceConfig, ImportSourceInterface::class);
        $target = $this-&amp;gt;makeAdapter($targetConfig, ImportTargetInterface::class);

        $target-&amp;gt;store(
            $source-&amp;gt;fetch()
        );

    }

    protected function makeAdapter(array $config, string $interface): object
    {
        $instance = app()-&amp;gt;makeWith($config['class'], $config['arguments'] ?? []);

        if (! $instance instanceof $interface) {
            throw new \RuntimeException('Adapter does not implement required interface.');
        }

        return $instance;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the heart of the system: given a job name, it dynamically resolves the correct adapters and runs the import.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Functional Tests&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I wrote tests to cover each combination:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSV -&amp;gt; MySQL&lt;/li&gt;
&lt;li&gt;CSV -&amp;gt; File&lt;/li&gt;
&lt;li&gt;HTTP -&amp;gt; MySQL&lt;/li&gt;
&lt;li&gt;HTTP -&amp;gt; File
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace Tests\Feature;

use App\Adapters\Sources\CsvFileSource;
use App\Adapters\Sources\HttpClientSource;
use App\Adapters\Targets\EloquentStorage;
use App\Adapters\Targets\FileStorage;
use App\Services\ImportJobRunner;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class ImportJobRunnerTest extends TestCase
{
    use RefreshDatabase;

    #[Test]
    public function it_imports_from_csv_to_mysql(): void
    {
        config()-&amp;gt;set('import_jobs.users_from_csv', [
            'source' =&amp;gt; 'csv_users',
            'target' =&amp;gt; 'mysql',
        ]);

        config()-&amp;gt;set('import_sources.csv_users', [
            'class' =&amp;gt; CsvFileSource::class,
            'arguments' =&amp;gt; [
                'path' =&amp;gt; storage_path('imports/users.csv'),
            ],
        ]);

        config()-&amp;gt;set('import_targets.mysql', [
            'class' =&amp;gt; EloquentStorage::class,
            'arguments' =&amp;gt; [],
        ]);

        (new ImportJobRunner('users_from_csv'))-&amp;gt;run();

        $this-&amp;gt;assertDatabaseCount('imported_users', 4);
        $this-&amp;gt;assertDatabaseHas('imported_users', ['email' =&amp;gt; 'alice@example.com']);
        $this-&amp;gt;assertDatabaseHas('imported_users', ['email' =&amp;gt; 'bob@example.com']);
        $this-&amp;gt;assertDatabaseHas('imported_users', ['email' =&amp;gt; 'carla@example.com']);
        $this-&amp;gt;assertDatabaseHas('imported_users', ['email' =&amp;gt; 'mohamed@example.com']);

    }

    #[Test]
    public function it_imports_from_csv_to_file(): void
    {
        config()-&amp;gt;set('import_jobs.csv_to_file', [
            'source' =&amp;gt; 'csv_users',
            'target' =&amp;gt; 'file',
        ]);

        config()-&amp;gt;set('import_sources.csv_users', [
            'class' =&amp;gt; CsvFileSource::class,
            'arguments' =&amp;gt; [
                'path' =&amp;gt; storage_path('imports/users.csv'),
            ],
        ]);

        config()-&amp;gt;set('import_targets.file', [
            'class' =&amp;gt; FileStorage::class,
            'arguments' =&amp;gt; [
                'path' =&amp;gt; storage_path('imports/users.jsonl'),
            ],
        ]);

        (new ImportJobRunner('csv_to_file'))-&amp;gt;run();

        $this-&amp;gt;assertFileExists(storage_path('imports/users.jsonl'));
        $content = File::get(storage_path('imports/users.jsonl'));

        $this-&amp;gt;assertStringContainsString('mohamed@example.com', $content);
        $this-&amp;gt;assertCount(4, json_decode($content, true));
    }

    #[Test]
    public function it_imports_from_http_to_mysql(): void
    {
        Http::fake([
            '*' =&amp;gt; Http::response([
                ['email' =&amp;gt; 'foo@example.com', 'name' =&amp;gt; 'Foo'],
                ['email' =&amp;gt; 'bar@example.com', 'name' =&amp;gt; 'Bar'],
            ]),
        ]);

        config()-&amp;gt;set('import_jobs.http_to_mysql', [
            'source' =&amp;gt; 'http_users',
            'target' =&amp;gt; 'mysql',
        ]);

        config()-&amp;gt;set('import_sources.http_users', [
            'class' =&amp;gt; HttpClientSource::class,
            'arguments' =&amp;gt; [
                'url' =&amp;gt; 'https://dummy.test/api/users',
                'token' =&amp;gt; 'dummy_token',
            ],
        ]);

        config()-&amp;gt;set('import_targets.mysql', [
            'class' =&amp;gt; EloquentStorage::class,
            'arguments' =&amp;gt; [],
        ]);

        (new ImportJobRunner('http_to_mysql'))-&amp;gt;run();

        $this-&amp;gt;assertDatabaseCount('imported_users', 2);
        $this-&amp;gt;assertDatabaseHas('imported_users', ['email' =&amp;gt; 'foo@example.com']);
        $this-&amp;gt;assertDatabaseHas('imported_users', ['email' =&amp;gt; 'bar@example.com']);
    }

    #[Test]
    public function it_imports_from_http_to_file(): void
    {
        Http::fake([
            '*' =&amp;gt; Http::response([
                ['email' =&amp;gt; 'zara@example.com', 'name' =&amp;gt; 'Zara'],
            ]),
        ]);

        config()-&amp;gt;set('import_jobs.http_to_file', [
            'source' =&amp;gt; 'http_users',
            'target' =&amp;gt; 'file',
        ]);

        config()-&amp;gt;set('import_sources.http_users', [
            'class' =&amp;gt; HttpClientSource::class,
            'arguments' =&amp;gt; [
                'url' =&amp;gt; 'https://dummy.test/api/users',
                'token' =&amp;gt; 'dummy_token',
            ],
        ]);

        config()-&amp;gt;set('import_targets.file', [
            'class' =&amp;gt; FileStorage::class,
            'arguments' =&amp;gt; [
                'path' =&amp;gt; storage_path('imports/users.jsonl'),
            ],
        ]);

        (new ImportJobRunner('http_to_file'))-&amp;gt;run();

        $this-&amp;gt;assertFileExists(storage_path('imports/users.jsonl'));
        $this-&amp;gt;assertStringContainsString('zara@example.com', File::get(storage_path('imports/users.jsonl')));
    }

    #[Test]
    public function it_throws_if_adapter_does_not_implement_interface(): void
    {
        config()-&amp;gt;set('import_jobs.invalid_adapter', [
            'source' =&amp;gt; 'invalid_source',
            'target' =&amp;gt; 'mysql',
        ]);

        config()-&amp;gt;set('import_sources.invalid_source', [
            'class' =&amp;gt; \stdClass::class, // Ne respecte pas ImportSourceInterface
            'arguments' =&amp;gt; [],
        ]);

        $this-&amp;gt;expectException(\RuntimeException::class);
        $this-&amp;gt;expectExceptionMessage('Adapter does not implement required interface');

        (new ImportJobRunner('invalid_adapter'))-&amp;gt;run();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure the correct integration between source &amp;amp; target&lt;/li&gt;
&lt;li&gt;Test that the contracts are respected&lt;/li&gt;
&lt;li&gt;Prove that the system can adapt dynamically to config changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Ports &amp;amp; Adapters isn’t just a theory — it’s a powerful way to write maintainable code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your business logic doesn’t know how data is fetched or stored.&lt;/li&gt;
&lt;li&gt;Your adapters don’t know why they’re used — just how.&lt;/li&gt;
&lt;li&gt;Your application becomes easier to test and evolve.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Github repository : &lt;a href="https://github.com/mouize/demo-ports-adapter" rel="noopener noreferrer"&gt;https://github.com/mouize/demo-ports-adapter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>php</category>
      <category>designpatterns</category>
      <category>architecture</category>
      <category>solidprinciples</category>
    </item>
    <item>
      <title>CQRS with Laravel: What It Is, When to Use It, and How I Implemented It</title>
      <dc:creator>saad mouizina</dc:creator>
      <pubDate>Mon, 28 Apr 2025 11:16:43 +0000</pubDate>
      <link>https://dev.to/pixelkk/cqrs-with-laravel-what-it-is-when-to-use-it-and-how-i-implemented-it-2lpg</link>
      <guid>https://dev.to/pixelkk/cqrs-with-laravel-what-it-is-when-to-use-it-and-how-i-implemented-it-2lpg</guid>
      <description>&lt;p&gt;As business logic becomes more complex, I’ve often found myself dealing with bloated services, classes that do too much, and tests that are hard to maintain. That’s when I started applying the &lt;strong&gt;CQRS&lt;/strong&gt; pattern (Command Query Responsibility Segregation) in my Laravel projects, and honestly, it’s been a game changer.&lt;/p&gt;

&lt;p&gt;In this article, I’ll explain what &lt;strong&gt;CQRS&lt;/strong&gt; is, when it makes sense to use it (and when not), how to implement it in Laravel, and walk you through a concrete use case: generating a monthly invoice.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is CQRS?
&lt;/h3&gt;

&lt;p&gt;CQRS is an architectural pattern that separates &lt;strong&gt;commands&lt;/strong&gt; aka operations that change the system’s statefrom &lt;strong&gt;queries&lt;/strong&gt; , which only read data.&lt;/p&gt;

&lt;p&gt;Why bother separating them?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It reflects &lt;strong&gt;business intent&lt;/strong&gt; more clearly in code&lt;/li&gt;
&lt;li&gt;It makes &lt;strong&gt;unit testing&lt;/strong&gt; much easier&lt;/li&gt;
&lt;li&gt;It keeps classes &lt;strong&gt;small and focused&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;In some cases, it improves &lt;strong&gt;performance&lt;/strong&gt; by optimizing read and write paths separately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is more than just using different methods: we create separate classes for each action, typically organized under Commands/ and Queries/, each with its own handler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up CQRS in Laravel
&lt;/h3&gt;

&lt;p&gt;To keep things clean and easy to find, I usually organize commands and queries under a CQRS folder. Here's a typical folder structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
├── CQRS/
│ ├── Commands/
│ │ ├── GenerateMonthlyInvoiceCommand.php
│ │ └── GenerateMonthlyInvoiceHandler.php
│ └── Queries/
│ ├── GetInvoiceBreakdownQuery.php
│ └── GetInvoiceBreakdownHandler.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation makes it very easy to reason about your application’s use cases at a glance.&lt;/p&gt;

&lt;h4&gt;
  
  
  A simple Command Bus for CQRS
&lt;/h4&gt;

&lt;p&gt;To implement CQRS properly in Laravel, you’ll use a &lt;strong&gt;command bus that&lt;/strong&gt; takes care of &lt;strong&gt;automatically finding the right handler&lt;/strong&gt; for any command or query based on a simple naming convention :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Command ➔ CommandHandler,&lt;/li&gt;
&lt;li&gt;Query ➔ QueryHandler).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s a simple version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Services;

use Illuminate\Pipeline\Pipeline;

class CommandBus
{
    public function __construct(
        protected array $middlewares = []
    ) {}

    public function dispatch(object $command): mixed
    {
        $handler = $this-&amp;gt;resolveHandler($command);

        return app(Pipeline::class)
            -&amp;gt;send($command)
            -&amp;gt;through($this-&amp;gt;middlewares)
            -&amp;gt;then(fn ($command) =&amp;gt; $handler-&amp;gt;handle($command));
    }

    protected function resolveHandler(object $command): object
    {
        $handlerClass = get_class($command) . 'Handler';

        if (!class_exists($handlerClass)) {
            throw new \RuntimeException("Handler [{$handlerClass}] not found for command " . get_class($command));
        }

        return app($handlerClass);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also gives the flexibility to &lt;strong&gt;add middlewares&lt;/strong&gt; around the execution, for example to log, trace, or validate commands before they are actually handled. If you want to use middlewares like logging or tracing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Services\Middlewares;

use Closure;

class LogCommandExecution
{
    public function handle(object $command, Closure $next)
    {
        logger()-&amp;gt;info('Executing command: ' . get_class($command));

        return $next($command);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, just register it in your service provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use App\Services\CommandBus;

public function register()
{
    $this-&amp;gt;app-&amp;gt;singleton(CommandBus::class, function () {
        return new CommandBus([
            // List your middlewares here if needed
            \App\Services\Middlewares\LogCommandExecution::class,
        ]);
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Common structure
&lt;/h4&gt;

&lt;p&gt;For &lt;strong&gt;writes&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Command: holds the input data&lt;/li&gt;
&lt;li&gt;CommandHandler: contains the business logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;strong&gt;reads&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query: describes the data you want&lt;/li&gt;
&lt;li&gt;QueryHandler: handles the logic and returns a DTO or array&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Naming your commands and queries
&lt;/h3&gt;

&lt;p&gt;Naming matters. In CQRS, &lt;strong&gt;your class names should reflect a business intent&lt;/strong&gt; , not a technical action.&lt;/p&gt;

&lt;p&gt;Prefer explicit, intention-revealing names:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GenerateMonthlyInvoiceCommand&lt;/li&gt;
&lt;li&gt;AssignRoleToUserCommand&lt;/li&gt;
&lt;li&gt;GetInvoiceBreakdownQuery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid generic names like InvoiceService, Handler, DoStuff, etc.&lt;/p&gt;

&lt;p&gt;Ideally, a non-technical stakeholder should be able to understand what a command does just by reading its name.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should you use CQRS?
&lt;/h3&gt;

&lt;p&gt;ou don’t need CQRS everywhere. For simple CRUD, it’s probably overkill. But it becomes very useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have rich business rules (state checks, validations, calculations)&lt;/li&gt;
&lt;li&gt;You need to aggregate data before writing&lt;/li&gt;
&lt;li&gt;You want to structure your code around business use cases&lt;/li&gt;
&lt;li&gt;You’re &lt;a href="https://dev.to/pixelkk/taming-laravel-monoliths-with-modular-architecture-1alh-temp-slug-49313"&gt;building modular&lt;/a&gt; or DDD-style architectures&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real-world example: generating a monthly invoice
&lt;/h3&gt;

&lt;p&gt;Let’s take a more realistic example than the usual “create a blog post”: generating a &lt;strong&gt;monthly invoice&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  The context
&lt;/h4&gt;

&lt;p&gt;At the end of each month, the system must generate an invoice for a customer based on multiple data sources (subscriptions, one-time services, product usage…). Before saving the invoice, it needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve all usage records for the month&lt;/li&gt;
&lt;li&gt;Calculate the subtotal, taxes, and possible discounts&lt;/li&gt;
&lt;li&gt;Build invoice line items&lt;/li&gt;
&lt;li&gt;Save everything to the database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The command&lt;/strong&gt;  : GenerateMonthlyInvoiceCommand&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class GenerateMonthlyInvoiceCommand
{
    public function __construct(
        public readonly int $customerId,
        public readonly DateTimeInterface $month
    ) {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The handler&lt;/strong&gt;  :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class GenerateMonthlyInvoiceHandler
{
    public function __construct(
        private ConsumptionRepository $consumptions,
        private InvoiceRepository $invoices,
        private TaxService $taxService,
        private DiscountService $discountService,
    ) {}

    public function handle(GenerateMonthlyInvoiceCommand $command): void
    {
        $items = $this-&amp;gt;consumptions-&amp;gt;forCustomerAndMonth($command-&amp;gt;customerId, $command-&amp;gt;month);

        if ($items-&amp;gt;isEmpty()) {
            throw new DomainException('No usage to invoice.');
        }

        $subtotal = $items-&amp;gt;sum(fn($item) =&amp;gt; $item-&amp;gt;price);
        $taxes = $this-&amp;gt;taxService-&amp;gt;compute($command-&amp;gt;customerId, $subtotal);
        $discount = $this-&amp;gt;discountService-&amp;gt;apply($command-&amp;gt;customerId, $items);
        $total = $subtotal + $taxes - $discount;

        $invoice = new Invoice(
            customerId: $command-&amp;gt;customerId,
            month: $command-&amp;gt;month,
            subtotal: $subtotal,
            taxes: $taxes,
            discount: $discount,
            total: $total
        );

        $invoice-&amp;gt;addLinesFromConsumption($items);
        $this-&amp;gt;invoices-&amp;gt;save($invoice);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;*&lt;em&gt;The query *&lt;/em&gt; : GetInvoiceBreakdownQuery&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class GetInvoiceBreakdownQuery
{
    public function __construct(
        public readonly int $customerId,
        public readonly DateTimeInterface $month
    ) {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The handler&lt;/strong&gt;  :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class GetInvoiceBreakdownHandler
{
    public function __construct(private InvoiceRepository $invoices) {}

    public function handle(GetInvoiceBreakdownQuery $query): InvoiceDTO
    {
        $invoice = $this-&amp;gt;invoices-&amp;gt;findByCustomerAndMonth($query-&amp;gt;customerId, $query-&amp;gt;month);
        return new InvoiceDTO($invoice);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits I noticed in my project
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clarity&lt;/strong&gt; : command and query names clearly describe business actions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolation&lt;/strong&gt; : each handler is easy to test independently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organization&lt;/strong&gt; : fits perfectly in a modular architecture&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt; : you can easily plug in validation, logging, or events without bloating your controllers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing handlers
&lt;/h3&gt;

&lt;p&gt;CQRS makes unit testing much simpler: each command or query is an isolated unit that you can test without going through a controller or infrastructure.&lt;/p&gt;

&lt;p&gt;Here’s a typical test for a command handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test('it generates an invoice with correct totals') {
    $consumptions = collect([
        new ConsumptionItem(price: 100),
        new ConsumptionItem(price: 200),
    ]);

    $handler = new GenerateMonthlyInvoiceHandler(
        new InMemoryConsumptionRepository($consumptions),
        new FakeInvoiceRepository(),
        new FixedTaxService(20),
        new FixedDiscountService(30)
    );

    $handler-&amp;gt;handle(new GenerateMonthlyInvoiceCommand(1, now()));

    expect(FakeInvoiceRepository::$savedInvoice)-&amp;gt;not-&amp;gt;toBeNull();
    expect(FakeInvoiceRepository::$savedInvoice-&amp;gt;total)-&amp;gt;toBe(290); // 300 + 20 - 30
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;CQRS is a great tool to help structure your code when business logic starts getting more complex (&lt;a href="https://dev.to/pixelkk/taming-laravel-monoliths-with-modular-architecture-1alh-temp-slug-49313"&gt;would fit perfectly in this use case&lt;/a&gt;). It makes your intentions explicit, your responsibilities more focused, and your code easier to test.&lt;/p&gt;

&lt;p&gt;You don’t need it everywhere, but when your services start feeling heavy or messy, consider giving it a try. Personally, I wouldn’t go back on larger projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus: CQRS vs CQS
&lt;/h3&gt;

&lt;p&gt;People often confuse CQRS with CQS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CQS (Command Query Separation)&lt;/strong&gt; is a design principle : a method should either &lt;strong&gt;modify state&lt;/strong&gt; (command) or &lt;strong&gt;return data&lt;/strong&gt; (query). &lt;strong&gt;NEVER both&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CQRS&lt;/strong&gt; &lt;strong&gt;(Command Query Responsibility Segregation)&lt;/strong&gt; takes this principle further by applying it at the &lt;strong&gt;application level&lt;/strong&gt; : separating models, services, and logic for reads and writes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;CQRS command&lt;/strong&gt; can contain some reads (e.g., find a customer before modifying it)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;CQS method&lt;/strong&gt; must do only one thing: read or write, not both&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CQRS is structural; CQS is behavioral.&lt;/p&gt;

</description>
      <category>php</category>
      <category>cleanarchitecture</category>
      <category>laravel</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Modular Architecture with Laravel</title>
      <dc:creator>saad mouizina</dc:creator>
      <pubDate>Sat, 12 Apr 2025 14:43:08 +0000</pubDate>
      <link>https://dev.to/pixelkk/modular-architecture-with-laravel-3593</link>
      <guid>https://dev.to/pixelkk/modular-architecture-with-laravel-3593</guid>
      <description>&lt;p&gt;Taming Laravel Monoliths with Modular Architecture&lt;/p&gt;

&lt;p&gt;As Laravel projects grow, maintaining a clean structure becomes increasingly difficult. Controllers pile up, models get bloated, and domain logic starts to leak across unrelated parts of the application. You quickly end up with a monolith that’s hard to scale or reason about.&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;modular architecture&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why modular architecture?
&lt;/h3&gt;

&lt;p&gt;The core idea is to break your application into &lt;strong&gt;feature-specific modules&lt;/strong&gt; , each &lt;strong&gt;responsible&lt;/strong&gt; for its own domain logic — authentication, blog, billing, etc. Each module contains its own models, controllers, routes, validation, services, and more.&lt;/p&gt;

&lt;p&gt;This approach offers several key benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separation of concerns&lt;/strong&gt;  — every module owns its logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved maintainability&lt;/strong&gt;  — easier to test, extend, or replace parts of the app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better scalability&lt;/strong&gt;  — especially with larger teams or growing business domains&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Modular architecture is particularly useful in mid-to-large-scale applications, where clear functional boundaries exist across features.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  What we’ll build
&lt;/h3&gt;

&lt;p&gt;In this tutorial, I’ll walk you through how to implement a clean modular structure in Laravel using the excellent &lt;a href="https://github.com/nWidart/laravel-modules" rel="noopener noreferrer"&gt;&lt;strong&gt;nwidart/laravel-modules&lt;/strong&gt;&lt;/a&gt; package.&lt;/p&gt;

&lt;p&gt;We’ll build two core modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt;  — handling registration, login, email verification, logout, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blog&lt;/strong&gt;  — a feature module containing User, Post, and Comment models, with full REST APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Along the way, we’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to keep modules decoupled but still communicate cleanly&lt;/li&gt;
&lt;li&gt;to centralize authentication logic in the main app&lt;/li&gt;
&lt;li&gt;How to secure all routes from the main app while allowing modules to expose their logic&lt;/li&gt;
&lt;li&gt;How to structure repositories, requests, events, and resources in a scalable and maintainable way&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;For the business logic and API structure, I’ll build on top of what I introduced in &lt;a href="https://dev.to/pixelkk/how-to-build-a-rest-api-with-laravel-beginners-guide-5afd"&gt;this previous article&lt;/a&gt;, adapting it to a modular architecture — so it might be worth giving it a quick read first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Getting Started: Project Setup and Docker Configuration
&lt;/h3&gt;

&lt;p&gt;Before jumping into modularization, we need a solid Laravel base to work on — ideally with Docker so everything is isolated and reproducible across environments.&lt;/p&gt;

&lt;p&gt;If you’re not familiar with Docker or want a ready-to-go setup for Laravel, I recommend checking out this article:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://dev.to/pixelkk/getting-started-with-laravel-projects-using-docker-my-basic-configuration-43ji"&gt;Getting Started with Laravel Projects Using Docker — My Basic Configuration&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It walks through a minimal and efficient Docker setup using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP-FPM container&lt;/li&gt;
&lt;li&gt;Nginx for web server&lt;/li&gt;
&lt;li&gt;MySQL/MariaDB for database&lt;/li&gt;
&lt;li&gt;Mailhog for local email testing&lt;/li&gt;
&lt;li&gt;and volume bindings to keep everything in sync&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once your Docker environment is up and Laravel is running (you can use the make laravel-install) in the container, we can move to the modular part of the project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’ve made the full implementation of this tutorial available here: &lt;a href="https://github.com/mouize/demo-modular-architecture" rel="noopener noreferrer"&gt;https://github.com/mouize/demo-modular-architecture&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. Install the nwidart/laravel-modules package
&lt;/h3&gt;

&lt;p&gt;We’ll use the excellent &lt;a href="https://github.com/nWidart/laravel-modules" rel="noopener noreferrer"&gt;nwidart/laravel-modules&lt;/a&gt; package to structure our app into independent feature modules.&lt;/p&gt;

&lt;p&gt;Install it via Composer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require nwidart/laravel-modules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then publish the config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan vendor:publish --provider="Nwidart\Modules\LaravelModulesServiceProvider"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To ensure Composer can autoload our modules, update your composer.json with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"autoload": {
    "psr-4": {
        "Modules\\": "Modules/"
    }
},
"extra": {
    "merge-plugin": {
        "include": [
            "Modules/*/composer.json"
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer dump-autoload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can also customize how modules are generated by editing config/modules.php — for example, under the generator section, you can define which folders or files should be created by default for each module.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Setting up the Authentication Module
&lt;/h3&gt;

&lt;p&gt;The first feature we’re going to modularize is authentication — one of the most critical and commonly reused parts of any application.&lt;/p&gt;

&lt;p&gt;We’ll use Laravel Sanctum for token-based authentication, and organize the entire auth logic (registration, login, logout, email verification) inside a dedicated &lt;strong&gt;Authentication&lt;/strong&gt; module.&lt;/p&gt;

&lt;p&gt;But before diving into code, let’s understand the reasoning behind it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why isolate authentication into a module?
&lt;/h4&gt;

&lt;p&gt;Authentication often touches a lot of parts in an app (user creation, email verification, session/token management, etc.), so it makes sense to extract it into its own self-contained domain.&lt;/p&gt;

&lt;p&gt;By doing this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We &lt;strong&gt;reduce coupling&lt;/strong&gt; between authentication logic and the rest of the app&lt;/li&gt;
&lt;li&gt;We can &lt;strong&gt;reuse or replace&lt;/strong&gt; the module easily in other Laravel projects&lt;/li&gt;
&lt;li&gt;We keep things organized and scalable — one module, one responsibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Create the authentication module
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan module:make Authentication
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scaffolds a fully isolated folder structure under Modules/Authentication, including space for models, controllers, routes, requests, events, etc.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create the AuthController
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace Modules\Authentication\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Event;
use Modules\Authentication\Http\Requests\RegisterRequest;
use Modules\Authentication\Http\Requests\LoginRequest;
use Modules\Authentication\Contracts\UserRepositoryInterface;
use Modules\Authentication\Events\UserRegistered;
use Modules\Authentication\Events\UserVerified;

class AuthController extends Controller
{
    public function __construct(protected UserRepositoryInterface $userRepository) {}

    public function register(RegisterRequest $request): JsonResponse
    {
        $user = $this-&amp;gt;userRepository-&amp;gt;register([
            'name' =&amp;gt; $request-&amp;gt;validated('name'),
            'email' =&amp;gt; $request-&amp;gt;validated('email'),
            'password' =&amp;gt; Hash::make($request-&amp;gt;validated('password'),
        ]);

        Event::dispatch(new UserRegistered($user));

        return response()-&amp;gt;json([
            'message' =&amp;gt; 'Account created successfully. Please check your email to verify your account.',
        ], 201);
    }

    public function verify(Request $request): JsonResponse
    {
        $user = $this-&amp;gt;userRepository-&amp;gt;findOrFail($request-&amp;gt;route('id'));

        if (! hash_equals((string) $request-&amp;gt;route('hash'), sha1($user-&amp;gt;getEmail()))) {
            return response()-&amp;gt;json(['message' =&amp;gt; 'Invalid verification link'], 400);
        }

        Event::dispatch(new UserVerified($user));

        return response()-&amp;gt;json(['message' =&amp;gt; 'Email verified successfully']);
    }

    public function login(LoginRequest $request): JsonResponse
    {
        $user = $this-&amp;gt;userRepository-&amp;gt;getUserByEmail($request-&amp;gt;validated('email'));

        if (! $user || ! Hash::check($request-&amp;gt;validated('password'), $user-&amp;gt;getPassword())) {
            return response()-&amp;gt;json(['message' =&amp;gt; 'Invalid credentials'], 401);
        }

        $token = $user-&amp;gt;createToken('auth_token');

        return response()-&amp;gt;json([
            'access_token' =&amp;gt; $token-&amp;gt;plainTextToken,
            'token_type' =&amp;gt; 'Bearer',
        ]);
    }

    public function logout(Request $request): JsonResponse
    {
        $request-&amp;gt;user()-&amp;gt;tokens()-&amp;gt;delete();

        return response()-&amp;gt;json(['message' =&amp;gt; 'Successfully logged out']);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Add FormRequest validation
&lt;/h4&gt;

&lt;p&gt;To keep our controller clean and follow Laravel best practices, we use &lt;strong&gt;FormRequest classes&lt;/strong&gt; to validate the inputs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan module:make-request RegisterRequest Authentication
php artisan module:make-request LoginRequest Authentication
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then define rules in each request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//Modules/Authentication/Http/Requests/RegisterRequest.php
public function rules(): array
{
    return [
        'name' =&amp;gt; ['required', 'string'],
        'email' =&amp;gt; ['required', 'email', 'unique:users,email'],
        'password' =&amp;gt; ['required', 'min:8'],
    ];
}

//Modules/Authentication/Http/Requests/LoginRequest.php
public function rules(): array
{
    return [
        'email' =&amp;gt; ['required', 'email'],
        'password' =&amp;gt; ['required'],
    ];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Define contracts to decouple logic
&lt;/h4&gt;

&lt;p&gt;To keep our module independent from the actual User implementation, we rely on &lt;strong&gt;interfaces&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This allows the main application to define how users are created, authenticated, and managed — while the module simply expects any class that fulfills the contract.&lt;/p&gt;

&lt;p&gt;Create two interfaces inside your module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan module:make-interface UserInterface Authentication
php artisan module:make-interface UserRepositoryInterface Authentication

namespace Modules\Authentication\Contracts;

interface UserInterface
{
    public function getId(): int;
    public function getEmail(): string;
    public function getPassword(): string;
    public function createToken(string $name);
}

namespace Modules\Authentication\Contracts;

interface UserRepositoryInterface
{
    public function register(array $data): UserInterface;
    public function getUserByEmail(string $email): ?UserInterface;
    public function findOrFail(int $id): UserInterface;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Use events to externalize side-effects
&lt;/h4&gt;

&lt;p&gt;Instead of triggering things like email notifications or verification logic directly inside the controller, we use &lt;strong&gt;events&lt;/strong&gt; to handle side effects.&lt;/p&gt;

&lt;p&gt;This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear separation of responsibilities&lt;/li&gt;
&lt;li&gt;Flexibility to change what happens after registration without touching the controller
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan module:make-event UserRegistered Authentication
php artisan module:make-event UserVerified Authentication
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define them like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace Modules\Authentication\Events;

use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Modules\Authentication\Interfaces\UserInterface;

class UserRegistered
{
    use Dispatchable, SerializesModels;

    /**
     * Create a new event instance.
     */
    public function __construct(public UserInterface $user) {}
}

use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Modules\Authentication\Interfaces\UserInterface;

class UserVerified
{
    use Dispatchable, SerializesModels;

    /**
     * Create a new event instance.
     */
    public function __construct(public UserInterface $user) {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Creating the Blog Module
&lt;/h3&gt;

&lt;p&gt;Now that our authentication module is in place, let’s move on to building a second module — the &lt;strong&gt;Blog&lt;/strong&gt;  module.&lt;/p&gt;

&lt;p&gt;This will represent a real business feature and give us the opportunity to organize models, resources, relationships, validation, and repositories in a modular way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan module:make Blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s generate our User, Post, and Comment models using the built-in Nwidart generator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan module:make-model User Blog -frRc
php artisan module:make-model Post Blog -frRc
php artisan module:make-model Comment Blog -frRc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/mouize/demo-modular-architecture/tree/main/app/Modules/Blog/app/Models" rel="noopener noreferrer"&gt;We can now define for each model&lt;/a&gt; the relationships and fillable attributes inside each one.&lt;/p&gt;

&lt;p&gt;The -frRc flags we used when generating the models gave us a solid base: eloquent resources, form requests, and RESTful controllers were all scaffolded automatically.&lt;/p&gt;

&lt;p&gt;Now we just need to fill in the logic to bring it all together.&lt;/p&gt;

&lt;h4&gt;
  
  
  Migrations
&lt;/h4&gt;

&lt;p&gt;Create the corresponding migrations in a folder stubs/migrations &lt;a href="https://github.com/mouize/demo-modular-architecture/tree/main/app/Modules/Blog/stubs/migrations" rel="noopener noreferrer"&gt;as shown here.&lt;/a&gt; (will be discussed deeply in a next section)&lt;/p&gt;

&lt;h4&gt;
  
  
  Using Spatie Query Builder for Clean &amp;amp; Flexible Queries
&lt;/h4&gt;

&lt;p&gt;To keep our controllers clean and make our API flexible for consumers, we use &lt;a href="https://github.com/spatie/laravel-query-builder" rel="noopener noreferrer"&gt;Spatie Query Builder&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It allows us to build powerful filters, includes, and sorts with zero extra logic in the controller. All of that is handled inside the repository layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require spatie/laravel-query-builder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each module has its own composer.json, so we also need to add the package there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Modules/Blog/composer.json
"require": {
    "spatie/laravel-query-builder": "*" //You can set a specific version depending on your needs
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, regenerate the autoloader:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer dump-autoload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Repositories: Centralizing Data Access
&lt;/h4&gt;

&lt;p&gt;We can generate our repositories now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan module:make-repository UserRepository Blog
php artisan module:make-repository PostRepository Blog
php artisan module:make-repository CommentRepository Blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/mouize/demo-modular-architecture/tree/main/app/Modules/Blog/app/Repositories" rel="noopener noreferrer"&gt;Let’s complete them&lt;/a&gt; to encapsulate all the logic for querying and persisting data.&lt;/p&gt;

&lt;h4&gt;
  
  
  Http logics
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/mouize/demo-modular-architecture/tree/main/app/Modules/Blog/app/Http/Controllers" rel="noopener noreferrer"&gt;RESTful controllers&lt;/a&gt;, f&lt;a href="https://github.com/mouize/demo-modular-architecture/tree/main/app/Modules/Blog/app/Http/Requests" rel="noopener noreferrer"&gt;orm requests&lt;/a&gt;, &lt;a href="https://github.com/mouize/demo-modular-architecture/tree/main/app/Modules/Blog/app/Http/Resources" rel="noopener noreferrer"&gt;eloquent resources&lt;/a&gt; were also scaffolded, let’s plug them into the repositories and handle basic CRUD operations as shown here.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Publishing Module Routes &amp;amp; Migrations
&lt;/h3&gt;

&lt;p&gt;By design, modules should stay &lt;strong&gt;generic and reusable&lt;/strong&gt;. That’s why we avoid hardcoding things like route prefixes or middleware inside the module.&lt;/p&gt;

&lt;p&gt;Instead, we let the &lt;strong&gt;main application decide&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to group or secure the routes&lt;/li&gt;
&lt;li&gt;how to customize the migration structure if needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve that, we make our &lt;strong&gt;routes and migrations publishable&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Making routes publishable
&lt;/h4&gt;

&lt;p&gt;In both the Authentication and Blog modules, define routes in a simple way — no middleware, no prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//Modules/Authentication/Routes/api.php
use Illuminate\Support\Facades\Route;
use Modules\Authentication\Http\Controllers\AuthController;

Route::post('register', [AuthController::class, 'register']);
Route::post('login', [AuthController::class, 'login']);
Route::post('logout', [AuthController::class, 'logout']);
Route::get('verify/{id}/{hash}', [AuthController::class, 'verify']);

//Modules/Blog/Routes/api.php
use Illuminate\Support\Facades\Route;
use Modules\Blog\Http\Controllers\UserController;
use Modules\Blog\Http\Controllers\PostController;
use Modules\Blog\Http\Controllers\CommentController;

Route::apiResource('users', UserController::class);
Route::apiResource('posts', PostController::class);
Route::apiResource('comments', CommentController::class);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;By avoiding hardcoded route groups, modules stay clean and composable — the main app is free to apply auth, prefixes, or rate limiting globally.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then in your module service provider, add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//Modules/Authentication/app/Providers/AuthenticationServiceProvider
$this-&amp;gt;publishes([
    __DIR__.'/../../routes/api.php' =&amp;gt; base_path('routes/modules/Authentication/api.php'),
], 'authentication-routes');

//Modules/Blog/app/Providers/BlogServiceProvider
$this-&amp;gt;publishes([
    __DIR__.'/../../routes/api.php' =&amp;gt; base_path('routes/modules/Blog/api.php'),
], 'blog-routes');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the main app, you can now publish and control the routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan vendor:publish --tag=authentication-routes
php artisan vendor:publish --tag=blog-routes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll get fully editable copies under routes/modules/Blog/api.php and routes/modules/Authentication/api.php.&lt;/p&gt;

&lt;p&gt;Once published, the app can load these routes with proper middleware and prefixing.&lt;/p&gt;

&lt;p&gt;(We’ll see that in the next section.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Making migrations publishable
&lt;/h4&gt;

&lt;p&gt;Same idea for migrations. If the app needs to add extra columns or adapt table structures, it can override the module’s migrations.&lt;/p&gt;

&lt;p&gt;In the same service providers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//Modules/Authentication/app/Providers/AuthenticationServiceProvider
$this-&amp;gt;publishes([
    __DIR__.'/../../stubs/migrations' =&amp;gt; database_path('migrations'),
], 'authentication-migrations');

//Modules/Blog/app/Providers/BlogServiceProvider
$this-&amp;gt;publishes([
    __DIR__.'/../../stubs/migrations' =&amp;gt; database_path('migrations'),
], 'blog-migrations');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; We moved the migrations to the stubs/migrations folder to prevent them from being executed during a migrate command unless they are explicitly published first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then publish them with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan vendor:publish --tag=authentication-migrations
php artisan vendor:publish --tag=blog-migrations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll get fully editable copies under database/migrations&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Orchester Modules from the Main Application
&lt;/h3&gt;

&lt;p&gt;Now that our modules are structured and publish their routes and migrations, the main application becomes the central &lt;strong&gt;orchestrator&lt;/strong&gt;. It will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect the app’s User model to the Authentication module&lt;/li&gt;
&lt;li&gt;Implement the UserRepositoryInterface and UserInterface&lt;/li&gt;
&lt;li&gt;Secure and load the module routes dynamically&lt;/li&gt;
&lt;li&gt;Listen to events (like UserRegistered) and decide how to react&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Create the main User model
&lt;/h4&gt;

&lt;p&gt;This model lives in the app and &lt;strong&gt;extends&lt;/strong&gt; the User model from the Blog module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Models;

use App\Mail\CustomVerifyEmail;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Modules\Authentication\Interfaces\UserInterface;
use Modules\Blog\Models\User as BlogUser;

class User extends BlogUser implements UserInterface, AuthenticatableContract
{
    use HasApiTokens, Notifiable, Authenticatable, MustVerifyEmail;

    protected $fillable = ['name', 'email', 'password'];

    public function getId(): int
    {
        return $this-&amp;gt;id;
    }

    public function getEmail(): string
    {
        return $this-&amp;gt;email;
    }

    public function getPassword(): string
    {
        return $this-&amp;gt;password;
    }

    public function sendEmailVerificationNotification(): void
    {
        $this-&amp;gt;notify(new CustomVerifyEmail);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This model handles authentication and implements the interface expected by the Authentication module.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Configure Laravel to use this User model
&lt;/h4&gt;

&lt;p&gt;In config/auth.php:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'providers' =&amp;gt; [
    'users' =&amp;gt; [
        'driver' =&amp;gt; 'eloquent',
        'model' =&amp;gt; App\Models\User::class,
    ],
],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Implement the UserRepository
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Repository;

use App\Models\User;
use Modules\Authentication\Interfaces\UserInterface;
use Modules\Authentication\Interfaces\UserRepositoryInterface;
use Modules\Blog\Repositories\UserRepository as BlogUserRepository;

class UserRepository extends BlogUserRepository implements UserRepositoryInterface
{
    protected string $model = User::class;

    public function register(array $data): UserInterface
    {
        return $this-&amp;gt;model::create($data);
    }

    public function getUserByEmail(string $email): ?UserInterface
    {
        return $this-&amp;gt;model::where('email', $email)-&amp;gt;first();
    }

    public function findOrFail(int $id): UserInterface
    {
        return $this-&amp;gt;model::findOrFail($id);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then bind the interface in your AppServiceProvider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Modules\Authentication\Contracts\UserRepositoryInterface;
use App\Repositories\UserRepository;

public function register()
{
    $this-&amp;gt;app-&amp;gt;bind(UserRepositoryInterface::class, UserRepository::class);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Listen to events
&lt;/h4&gt;

&lt;p&gt;Create listeners for the events dispatched in the Authentication module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Listeners;

use Modules\Authentication\Events\UserRegistered;

class SendEmailVerificationNotification
{
    public function handle(UserRegistered $event): void
    {
        $event-&amp;gt;user-&amp;gt;sendEmailVerificationNotification();
    }
}

namespace App\Listeners;

use Modules\Authentication\Events\UserVerified;

class MarkEmailAsVerified
{
    public function handle(UserVerified $event): void
    {
        $event-&amp;gt;user-&amp;gt;markEmailAsVerified();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register them in EventServiceProvider (if needed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Modules\Authentication\Events\UserRegistered;
use Modules\Authentication\Events\UserVerified;

protected $listen = [
    UserRegistered::class =&amp;gt; [
        \App\Listeners\SendEmailVerificationNotification::class,
    ],
    UserVerified::class =&amp;gt; [
        \App\Listeners\MarkEmailAsVerified::class,
    ],
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Secure routes
&lt;/h4&gt;

&lt;p&gt;Create a new service provider like ModuleRouteServiceProvider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Providers;

use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Nwidart\Modules\Facades\Module;

class ModuleRouteServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        foreach (Module::allEnabled() as $module) {
            $name = $module-&amp;gt;getName();
            $path = base_path("routes/modules/{$name}/api.php");

            if (file_exists($path)) {
                Route::prefix('api')
                    -&amp;gt;name('api.')
                    -&amp;gt;group($path);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If needed, declare this new ServiceProvider in bootstrap/providers&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the module routes are published (e.g. routes/modules/Blog/api.php), the main app can customize them freely — including applying authentication middleware.&lt;/p&gt;

&lt;p&gt;For example, in the Blog module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Illuminate\Support\Facades\Route;
use Modules\Blog\Http\Controllers\PostController;
use Modules\Blog\Http\Controllers\UserController;
use Modules\Blog\Http\Controllers\CommentController;

Route::middleware('auth:sanctum')-&amp;gt;group(function () {
    Route::apiResource('users', UserController::class);
    Route::apiResource('posts', PostController::class);
    Route::apiResource('comments', CommentController::class);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for the Authentication module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Illuminate\Support\Facades\Route;
use Modules\Authentication\Http\Controllers\AuthController;

Route::post('register', [AuthenticationController::class, 'register'])
    -&amp;gt;name('register');
Route::post('login', [AuthenticationController::class, 'login'])
    -&amp;gt;name('login');
Route::middleware('signed')
    -&amp;gt;get('/email/verify/{id}/{hash}', [AuthenticationController::class, 'verify'])
    -&amp;gt;name('verification.verify');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This gives you &lt;strong&gt;maximum control&lt;/strong&gt; over each module’s security behavior, without needing to maintain a custom route loader.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your app now orchestrates everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It owns the User logic&lt;/li&gt;
&lt;li&gt;Secures and loads module routes dynamically&lt;/li&gt;
&lt;li&gt;Handles auth-specific events&lt;/li&gt;
&lt;li&gt;Keeps the modules decoupled and reusable&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  And Voilà
&lt;/h3&gt;

&lt;p&gt;Modular architecture brings clarity, separation of concerns, and long-term maintainability to Laravel projects — especially when things start to grow.&lt;/p&gt;

&lt;p&gt;By leveraging &lt;a href="https://github.com/nWidart/laravel-modules" rel="noopener noreferrer"&gt;nwidart/laravel-modules&lt;/a&gt;, we’ve been able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Split our application into &lt;strong&gt;clearly defined domains&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Keep modules &lt;strong&gt;independent and reusable&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Centralize responsibilities like &lt;strong&gt;authentication, route security, and event handling&lt;/strong&gt; in the main app&lt;/li&gt;
&lt;li&gt;Maintain &lt;strong&gt;clean API logic&lt;/strong&gt; using repositories, resources, FormRequests, and Spatie Query Builder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This structure is particularly useful in real-world teams, where different developers work on different parts of the system, or where features evolve independently over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus: What about Domain-Driven Design (DDD)?
&lt;/h3&gt;

&lt;p&gt;Modular architecture is often confused with DDD — and while the two can overlap, they’re not the same.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modular architecture is about &lt;strong&gt;technical organization&lt;/strong&gt; : splitting your code by feature to make it more maintainable.&lt;/li&gt;
&lt;li&gt;DDD is about &lt;strong&gt;business modeling&lt;/strong&gt; : building your code around the language and rules of your domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can absolutely use both together — in fact, modules are a great place to implement bounded contexts and aggregates if you want to go deeper into DDD later on.&lt;/p&gt;

</description>
      <category>modulararchitecture</category>
      <category>softwaredevelopment</category>
      <category>php</category>
      <category>cleanarchitecture</category>
    </item>
    <item>
      <title>How to Build a REST API with Laravel (Beginner Guide — Part 3): Using Api Platform</title>
      <dc:creator>saad mouizina</dc:creator>
      <pubDate>Mon, 24 Mar 2025 15:31:42 +0000</pubDate>
      <link>https://dev.to/pixelkk/how-to-build-a-rest-api-with-laravel-beginner-guide-part-3-using-api-platform-2n16</link>
      <guid>https://dev.to/pixelkk/how-to-build-a-rest-api-with-laravel-beginner-guide-part-3-using-api-platform-2n16</guid>
      <description>&lt;p&gt;In the first article of this series, we went through the entire process of building a REST API with Laravel from scratch. We covered…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@PixelKK/how-to-build-a-rest-api-with-laravel-beginner-guide-part-3-using-api-platform-044ae89dd87a?source=rss-f07f7b3d3e91------2" rel="noopener noreferrer"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>apidevelopment</category>
      <category>php</category>
      <category>apiplatform</category>
    </item>
    <item>
      <title>How to Build a REST API with Laravel (Beginner Guide — part 2): Authentication with Sanctum</title>
      <dc:creator>saad mouizina</dc:creator>
      <pubDate>Thu, 20 Mar 2025 12:02:31 +0000</pubDate>
      <link>https://dev.to/pixelkk/how-to-build-a-rest-api-with-laravel-beginner-guide-part-2-authentication-with-sanctum-i4e</link>
      <guid>https://dev.to/pixelkk/how-to-build-a-rest-api-with-laravel-beginner-guide-part-2-authentication-with-sanctum-i4e</guid>
      <description>&lt;p&gt;In the first part of this series, we built a REST API with Laravel using Users, Posts, and Comments. Now, it’s time to add authentication…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@PixelKK/how-to-build-a-rest-api-with-laravel-part-2-authentication-with-sanctum-e88ce1a80440?source=rss-f07f7b3d3e91------2" rel="noopener noreferrer"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>apidevelopment</category>
      <category>php</category>
      <category>security</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>How to Build a REST API with Laravel (Beginner’s Guide)</title>
      <dc:creator>saad mouizina</dc:creator>
      <pubDate>Sun, 16 Mar 2025 14:10:12 +0000</pubDate>
      <link>https://dev.to/pixelkk/how-to-build-a-rest-api-with-laravel-beginners-guide-5afd</link>
      <guid>https://dev.to/pixelkk/how-to-build-a-rest-api-with-laravel-beginners-guide-5afd</guid>
      <description>&lt;p&gt;In this guide, we will walk through building a REST API in Laravel using Users, Posts, and Comments. We will cover:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@PixelKK/how-to-build-a-rest-api-with-laravel-beginners-guide-fff06c270ee9?source=rss-f07f7b3d3e91------2" rel="noopener noreferrer"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>php</category>
      <category>apidevelopment</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Getting Started with Laravel Projects Using Docker: My Basic Configuration</title>
      <dc:creator>saad mouizina</dc:creator>
      <pubDate>Thu, 13 Mar 2025 19:18:21 +0000</pubDate>
      <link>https://dev.to/pixelkk/getting-started-with-laravel-projects-using-docker-my-basic-configuration-43ji</link>
      <guid>https://dev.to/pixelkk/getting-started-with-laravel-projects-using-docker-my-basic-configuration-43ji</guid>
      <description>&lt;p&gt;As someone who frequently works with Laravel and Docker, I’ve found that Docker is an excellent tool for managing and isolating services…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@PixelKK/getting-started-with-laravel-projects-using-docker-my-basic-configuration-eb86d795dfa0?source=rss-f07f7b3d3e91------2" rel="noopener noreferrer"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>developmentenvironme</category>
      <category>docker</category>
      <category>laravel</category>
      <category>containerization</category>
    </item>
  </channel>
</rss>
