A Laravel project with a Domain-Driven Design (DDD) structure, basic configuration, and commonly used packages pre-installed and configured, to help you start building your next big application.
- PHP ^8.1
- Composer ^2.2
composer create-project abd-wazzan/laravel-api-boilerplate api-app
Install dependencies
cd api-app
composer install
Setup .env file
cp .env.example .env
Generate the application key
php artisan key:generate
Run Locally
php artisan serve
General:
Development:
- Laravel IDE Helper
- Scribe API documentation tool
- Laravel Telescope
- Pest Testing Framework
- Grum PHP
- Security Advisor
- DDD (Domain Driven Design)
- API Response Helper
- Scribe Api Tags
- Global Helper
- Migration Structure
- Polymorphic Mapping
- Database Seeders
- Shared Directory
- Enable Model Strict Mode
- Pest testing framework
Software development approach that tries to bring the business language and the source code as close as possible.
This structure is inspired by LARAVEL BEYOND CRUD.
Domain Layer Example:
src/Domain/Invoices/
├── Actions
├── QueryBuilders
├── Collections
├── Data
├── Events
├── Exceptions
├── Listeners
├── Models
├── Rules
└── States
src/Domain/Products/
├── Actions
└── .....
Application Layer Example:
The REST API application:
src/App/Api/
├── Products
├── Controllers
├── Middlewares
├── Requests
├── Queries
├── Filters
└── Resources
The Console application
src/App/Console/
└── Commands
The admin HTTP application:
src/App/Admin/
├── Products
├── Controllers
├── Middlewares
├── Requests
├── Resources
├── Queries
├── Filters
└── ViewModels
- Domain Oriented Laravel
- Working With Data
- Actions
- Models
- States
- Managing Domains
- Application Layer
- View Models
- Test Factories
A simple trait allowing consistent API responses throughout your Laravel application.
Method | Status |
---|---|
okResponse() |
200 |
createdResponse() |
201 |
failedResponse() |
400 |
unauthorizedResponse() |
401 |
forbiddenResponse() |
403 |
notFoundResponse() |
404 |
unprocessableResponse() |
422 |
serverErrorResponse() |
500 |
<?php
namespace App\Http\Api\Controllers;
use App\Traits\ApiResponseHelpers;
use Illuminate\Http\JsonResponse;
use App\Http\Controller;
class ProductController extends Controller
{
use ApiResponseHelper;
public function index(): JsonResponse
{
return $this->okResponse();
}
}
Additional scribe tags that match the ApiResponseHelper responses.
Tag | Status |
---|---|
@okResponse |
200 |
@createdResponse |
201 |
@failedResponse |
400 |
@unauthorizedResponse |
401 |
@forbiddenResponse |
403 |
@notFoundResponse |
404 |
@unprocessableResponse |
422 |
@serverErrorResponse |
500 |
Tag | Description |
---|---|
@usesPagination |
will add page[number] and page[size] to the query parameters |
<?php
namespace App\Http\Api\Controllers;
use App\Helpers\ApiController;
use App\Traits\ApiResponseHelpers;
use Illuminate\Http\JsonResponse;
use App\Http\Controller;
/**
* Class CategoryController
* @group Category
*/
class CategoryController extends Controller
{
use ApiResponseHelper;
/**
* Get Categories
*
* this request is used to get all categories.
*
* @queryParam filter[name]
*
* @usesPagination
* @failedResponse
* @forbiddenResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Throwable
*/
public function index(): Response
{
return CategoryResource::collection($categories->all());
}
}
Simple php file that contains you global functions, which you can find it in ./src/shared/Helpers/global.php
.
In order to group your migration files by their domains, you can create additional migration directories
and load them in the AppServiceProvider
using loadMigrationsFrom
function:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
$this->loadMigrationsFrom([
database_path().DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR.'Client',
]);
}
}
Please read this article first to identify the problem.
In order to achieve the morph mapping, we created the MorphEnum
that will contain each model morph key and then use it
in Relation::morphMap
function as shown in the example:
<?php
namespace Shared\Enums;
enum MorphEnum: string
{
case USER = 'user';
}
<?php
namespace App\Providers;
use Shared\Enums\MorphEnum;
use Domain\Client\Models\User;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Relation::morphMap([
MorphEnum::USER->value => User::class,
]);
}
}
We generally have two types of seeded data:
- Initial data: the project cannot function without it. For example, countries table data, and these data usually come from datasets.
- Fake data: for testing purposes that can fill up any table instead of manually inserting row by row, this data is usually generated by factories.
In order to prevent the fake data from being seeded in the production environment, we created a new seeder class
called TestingSeeder.php
which will contain all the fake data seeders and will only run in a non-production
environment. The normal seeders will stay in DatabaseSeeder.php
.
The src/shared/
directory is used for helper, traits, enums .... that are going to be used by the application and the domain.
I will be happy to hear your feedback! If you have any recommendation or suggestion, please send an e-mail to Mail.