In this lesson, we will discuss options to organize a larger project that can be divided into sub-projects. I will show you three ways.
One approach to structure larger projects is by sub-systems or modules, like expense management module, customer management module, etc.
You can have separate developers or teams working only on specific module(s). One way to do it is by using a package called nWidart/laravel-modules.
The package can be installed via composer.
composer require nwidart/laravel-modules
Optionally, you can publish the package's configuration file by running:
php artisan vendor:publish --provider="Nwidart\Modules\LaravelModulesServiceProvider"
Then, the Modules
folder must be added to the autoload in composer.
composer.json:
{ // ... "autoload": { "psr-4": { "App\\": "app/", "Modules\\": "Modules/", "Database\\Factories\\": "database/factories/", "Database\\Seeders\\": "database/seeders/" } }, // ...}
Don't forget to run
composer dump-autoload
afterward.
New modules are created using the artisan command.
php artisan module:make blog
This will create a module in the path Modules/Blog
with the following structure:
Modules/ ├── Blog/ ├──app ├── Http/ ├── Controllers/ ├── Providers/ ├── BlogServiceProvider.php ├── RouteServiceProvider.php ├── config/ ├── database/ ├── seeders/ ├── resources/ ├── assets/ ├── views/ ├── routes/ ├── api.php ├── web.php ├── composer.json ├── module.json ├── package.json ├── vite.config.js
Everything related to the blog goes inside this module's folder. The views are called similar to how packages work by first calling the module name blog::
.
For example, if you have index.blade.php
inside the Modules/Blog/resources/views
folder, you would call it using the blog::index
syntax.
You can check a YouTube video example of converting simple applications to modules here: Laravel Modules Demo: Make Your Project Modular.
And you can check the difference between converted applications in the GitHub here.
Another option to divide the functionality of a project is to create a package.
If you have more than one project, you may feel that some parts may be reusable in multiple projects. Then, it is not enough to have a module: a better way is to create a package, put it on GitHub, and reuse it in multiple projects.
One great example is from Spatie, a well-known company that has made many packages throughout the years. All those packages are made because they were reused in multiple projects.
For example, if we checked the composer.json
of one of their open-source projects, we would see many of their packages used.
{ // ... "require": { "php": "^8.1", "ext-curl": "*", "ext-dom": "*", // ... "laravel/framework": "^10.0", "spatie/browsershot": "^3.57.5", "spatie/commonmark-highlighter": "^3.0", "spatie/cpu-load-health-check": "^1.0.2", "spatie/laravel-backup": "^8.1.2", "spatie/laravel-comments-livewire": "^2.0", "spatie/laravel-feed": "^4.2", "spatie/laravel-flash": "^1.9", "spatie/laravel-health": "^1.22", "spatie/laravel-honeypot": "^4.3", "spatie/laravel-horizon-watcher": "^1.0", "spatie/laravel-ignition": "^2.0", "spatie/laravel-log-dumper": "^1.4.1", "spatie/laravel-mailcoach-mailer": "^1.0", "spatie/laravel-mailcoach-sdk": "^1.0", "spatie/laravel-markdown": "^2.2.4", "spatie/laravel-medialibrary": "^10.7", "spatie/laravel-menu": "^4.1", "spatie/laravel-missing-page-redirector": "^2.9.2", "spatie/laravel-model-info": "^1.4", "spatie/laravel-ray": "^1.32", "spatie/laravel-remote": "^1.3", "spatie/laravel-responsecache": "^7.4", "spatie/laravel-schedule-monitor": "^3.1", "spatie/laravel-site-search": "^2.1", "spatie/laravel-stubs": "^2.5", "spatie/laravel-tags": "^4.3.2", "spatie/laravel-tail": "^4.4", "spatie/pest-plugin-test-time": "^2.0", "spatie/security-advisories-health-check": "^0.0.2", "stripe/stripe-php": "^7.128", "symfony/http-client": "^6.2", "symfony/mailgun-mailer": "^6.2" }, // ...}
Impressive list, right?
This is one way of thinking about creating packages, trying to develop the set of features around something that you would use in your current project and could be reused in feature projects.
I would say there are two types of packages:
For the backend, the example can be spatie/laravel-medialibrary or spatie/laravel-tags. Both packages have their tables, and logic to a Model can be added by using trait.
A trendy package for the visual "type" is FilamentPHP. This package adds some logic and the visual layer for the whole panel, forms, tables, etc.
Making universal UI is impossible. However, building UI based on the backend package is a very common scenario.
Remember that if you create a package, it should be taken care of. Documentation should be made, bugs fixed, etc. But if the package becomes popular, it could pay off hugely as it did for Spatie.
To learn how to create a package, you can check the course How to Create Laravel Package: Step-by-Step Example. Another excellent package creation resource is http://www.laravelpackage.com.
Another approach is to have packages inside the same app and treat them like modules. Let's take a look at canyongbs/advisingapp, an open-source project that uses InterNACHI/modular package to have modules as internal packages.
The InterNACHI/modular package allows it to have the same directory structure as a regular Laravel project but only as a package.
Modules are created in the app-modules
directory. When a new module is created, it is added to the composer.json
as a required package. When the first module is created, the repositories
key is added to the composer.json
. We can see how it looks in the canyongbs/advisingapp open-source project with 30+ internal modules.
{ // ... "require": { "php": "8.2.*", "ext-gd": "*", "ext-pdo": "*", "awcodes/filament-tiptap-editor": "^3.2.17", "aws/aws-php-sns-message-validator": "^1.9", "aws/aws-sdk-php": "^3.295", "barryvdh/laravel-debugbar": "^3.9", "bvtterfly/model-state-machine": "^0.3.0", "canyon-gbs/advising-app-alert": "*", "canyon-gbs/advising-app-analytics": "*", "canyon-gbs/advising-app-application": "*", "canyon-gbs/advising-app-assistant": "*", "canyon-gbs/advising-app-audit": "*@dev", "canyon-gbs/advising-app-authorization": "*", "canyon-gbs/advising-app-campaign": "*", "canyon-gbs/advising-app-care-team": "*", "canyon-gbs/advising-app-caseload-management": "*", "canyon-gbs/advising-app-consent": "*", "canyon-gbs/advising-app-division": "*", "canyon-gbs/advising-app-engagement": "*", "canyon-gbs/advising-app-form": "*", "canyon-gbs/advising-app-in-app-communication": "*", "canyon-gbs/advising-app-integration-ai": "*", "canyon-gbs/advising-app-integration-aws-ses-event-handling": "*", "canyon-gbs/advising-app-integration-google-analytics": "*", "canyon-gbs/advising-app-integration-google-recaptcha": "*", "canyon-gbs/advising-app-integration-microsoft-clarity": "*", "canyon-gbs/advising-app-integration-twilio": "*", "canyon-gbs/advising-app-interaction": "*", "canyon-gbs/advising-app-inventory-management": "*", "canyon-gbs/advising-app-knowledge-base": "*", "canyon-gbs/advising-app-meeting-center": "*", "canyon-gbs/advising-app-notification": "*", "canyon-gbs/advising-app-portal": "*", "canyon-gbs/advising-app-prospect": "*", "canyon-gbs/advising-app-report": "*", "canyon-gbs/advising-app-service-management": "*", "canyon-gbs/advising-app-student-data-model": "*", "canyon-gbs/advising-app-survey": "*", "canyon-gbs/advising-app-task": "*", "canyon-gbs/advising-app-team": "*", "canyon-gbs/advising-app-theme": "*", "canyon-gbs/advising-app-timeline": "*", "canyon-gbs/advising-app-webhook": "*", "composer/composer": "^2.6.4", "filament/filament": "^3.1", // ... }, // ... "repositories": [ { "type": "path", "url": "app-modules/*", "options": { "symlink": true } }, { "type": "git", "url": "https://github.com/canyongbs/laravel-auditing" } ] }
If we look at any of the modules, they have the structure of a typical Laravel package.
And every module's composer.json
has a typical structure for a package.
{ "name": "canyon-gbs/advising-app-analytics", "description": "", "type": "library", "version": "1.0", "license": "proprietary", "require": { "filament/filament": "^3.0.0" }, "autoload": { "psr-4": { "AdvisingApp\\Analytics\\": "src/", "AdvisingApp\\Analytics\\Tests\\": "tests/", "AdvisingApp\\Analytics\\Database\\Factories\\": "database/factories/", "AdvisingApp\\Analytics\\Database\\Seeders\\": "database/seeders/" } }, "minimum-stability": "dev", "extra": { "laravel": { "providers": [ "AdvisingApp\\Analytics\\Providers\\AnalyticsServiceProvider" ] } }}
Then, in the src
folder is the same Laravel structure, which is the whole point of such modules' approach.
If you want to watch a video overview, there is a YouTube video: Laravel Modules as "Internal" Packages? Interesting Demo.