This is a self-contained Expense Management module built with Laravel 11+ using modular architecture via nwidart/laravel-modules. It includes full CRUD operations for managing expenses with clean architecture best practices including service layer, form request validation, events, notifications, and optional Swagger documentation and testing support.
- Modular folder structure (Modules/Expenses)
- CRUD operations for expenses
- Uses UUID as primary key
- Form Request validation
- Service layer abstraction
- Event (
ExpenseCreated) with Listener - Database Notification (upon expense creation)
- Filtering by category & date range
- API routes only
- Optional OpenAPI (Scribe)
- Feature test using PHPUnit
- JSON API formatting via Laravel Resource
- Laravel 11/12
- PHP 8.2+
- MySQL/PostgreSQL
nwidart/laravel-modules- Laravel Resources & Events
- Laravel Notifications
- Scribe (OpenAPI)
- PHPUnit
git clone https://github.com/anfathhifans/Laravel-Expense-Module.git
cd Laravel-Expense-Module
composer install
cp .env.example .env
php artisan key:generateUpdate your DB credentials:
DB_DATABASE=your_db
DB_USERNAME=your_user
DB_PASSWORD=your_passcomposer require nwidart/laravel-modules
php artisan vendor:publish --provider="Nwidart\Modules\LaravelModulesServiceProvider"php artisan migratephp artisan serveAll routes are prefixed with /api/expenses
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/expenses | List all expenses |
| POST | /api/expenses | Create new expense |
| PUT | /api/expenses/{id} | Update an expense |
| DELETE | /api/expenses/{id} | Delete an expense |
Run tests:
php artisan test
Generate docs:
php artisan scribe:generate
Docs will be available at:
/docs
Then visit:
http://localhost:8000/docs
Modules/
Expenses/
Config/
Database/
Entities/
Events/
Http/
Controllers/
Requests/
Listeners/
Notifications/
Providers/
Resources/
Routes/
Services/
- Categories are stored as string enums (no separate table)
- UUID is used for expense IDs
- Auth is out of scope
- Only one user context assumed
- Located at:
Modules/Expenses/Events/ExpenseCreated.php
namespace Modules\Expenses\Events;
use Illuminate\Queue\SerializesModels;
use Modules\Expenses\Entities\Expense;
class ExpenseCreated {
use SerializesModels;
public function __construct(public Expense $expense) {}
}- Located at:
Modules/Expenses/Listeners/SendExpenseNotification.php
namespace Modules\Expenses\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Modules\Expenses\Events\ExpenseCreated;
use Modules\Expenses\Notifications\ExpenseNotification;
use Illuminate\Support\Facades\Notification;
use App\Models\User; // Assumes a User model exists
class SendExpenseNotification implements ShouldQueue {
public function handle(ExpenseCreated $event): void {
$user = User::first();
Notification::route('database', $user->id)
->notify(new ExpenseNotification($event->expense));
}
}- Located at:
Modules/Expenses/Notifications/ExpenseNotification.php
namespace Modules\Expenses\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\DatabaseMessage;
use Modules\Expenses\Entities\Expense;
class ExpenseNotification extends Notification {
use Queueable;
public function __construct(public Expense $expense) {}
public function via($notifiable): array {
return ['database'];
}
public function toDatabase($notifiable): DatabaseMessage {
return new DatabaseMessage([
'title' => $this->expense->title,
'amount' => $this->expense->amount,
'expense_date' => $this->expense->expense_date,
]);
}
}- Modify:
Modules/Expenses/Services/ExpenseService.php
use Modules\Expenses\Events\ExpenseCreated;
public function create(array $data): Expense {
$expense = Expense::create($data);
event(new ExpenseCreated($expense));
return $expense;
}- File:
Modules/Expenses/Providers/EventServiceProvider.php
namespace Modules\Expenses\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Modules\Expenses\Events\ExpenseCreated;
use Modules\Expenses\Listeners\SendExpenseNotification;
class EventServiceProvider extends ServiceProvider {
protected $listen = [
ExpenseCreated::class => [
SendExpenseNotification::class,
],
];
}"providers": [
"Modules\\Expenses\\Providers\\EventServiceProvider"
]