Skip to Content
API v2Adding a Module

Adding a Module

1. Generate the module scaffold

php artisan module:make YourModule

This creates Modules/YourModule/ with the standard directory structure including providers, routes, config, database directories, and test directories.

2. Verify module activation

Check modules_statuses.json in the project root. The new module should be automatically added:

{ "YourModule": true }

3. Configure the RouteServiceProvider

Edit Modules/YourModule/app/Providers/RouteServiceProvider.php to set your API route prefix:

<?php namespace Modules\YourModule\Providers; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Support\Facades\Route; class RouteServiceProvider extends ServiceProvider { protected string $name = 'YourModule'; public function boot(): void { parent::boot(); } public function map(): void { $this->mapApiRoutes(); } protected function mapApiRoutes(): void { Route::prefix('your-module') ->name('your-module.') ->middleware(SubstituteBindings::class) ->group(module_path($this->name, '/routes/api.php')); } }

Note on prefix conventions: Some modules prefix with just the module name (e.g., properties, events), while others use api/ prefix (e.g., Bills uses api/bills). Choose based on whether the module is consumed by the mobile app (no api/ prefix) or the web app (api/ prefix). If adding CORS, include CorsMiddleware::class in the middleware array.

4. Define routes

Edit Modules/YourModule/routes/api.php:

<?php use Illuminate\Support\Facades\Route; use Modules\YourModule\Http\Controllers\YourController; // Public routes Route::get('/', [YourController::class, 'index']); // Authenticated routes Route::middleware('auth:legacy-jwt')->group(function () { Route::post('/', [YourController::class, 'store']); });

5. Create a model

php artisan module:make-model Item YourModule

Place models in Modules/YourModule/app/Models/. Follow existing patterns:

<?php namespace Modules\YourModule\Models; use Illuminate\Database\Eloquent\Model; class Item extends Model { protected $fillable = ['name', 'description']; }

6. Create a migration

php artisan module:make-migration create_items_table YourModule

Migrations go in Modules/YourModule/database/migrations/. They are auto-discovered (configured in config/modules.php under auto-discover.migrations).

7. Create a FormRequest

php artisan module:make-request StoreItemRequest YourModule
<?php namespace Modules\YourModule\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class StoreItemRequest extends FormRequest { public function authorize(): bool { return true; // Or use a policy } public function rules(): array { return [ 'name' => 'required|string|max:255', 'description' => 'nullable|string', ]; } }

8. Create a Service

Create Modules/YourModule/app/Services/ItemService.php manually (no artisan generator):

<?php namespace Modules\YourModule\Services; use Modules\YourModule\Models\Item; class ItemService { public function create(array $data): Item { return Item::create($data); } }

9. Create an API Resource

<?php namespace Modules\YourModule\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class ItemResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, 'description' => $this->description, 'created_at' => $this->created_at, ]; } }

10. Create a Controller

php artisan module:make-controller ItemController YourModule

Wire everything together:

<?php namespace Modules\YourModule\Http\Controllers; use App\Http\Controllers\Controller; use Illuminate\Http\JsonResponse; use Modules\YourModule\Http\Requests\StoreItemRequest; use Modules\YourModule\Http\Resources\ItemResource; use Modules\YourModule\Services\ItemService; class ItemController extends Controller { public function __construct( private ItemService $itemService ) {} public function index(): JsonResponse { $items = Item::all(); return ItemResource::collection($items)->response(); } public function store(StoreItemRequest $request): JsonResponse { $item = $this->itemService->create($request->validated()); return ItemResource::make($item)->response()->setStatusCode(201); } }

11. Register the module in phpunit.xml

If the module has tests, add the test directories to phpunit.xml:

<testsuite name="Feature"> <!-- existing directories --> <directory>Modules/YourModule/tests/Feature</directory> </testsuite>

12. Write tests

See testing.md for patterns. Create tests in Modules/YourModule/tests/Feature/:

<?php uses(Tests\FeatureTestCase::class); test('it lists items', function () { $response = $this->getJson('/your-module'); $response->assertStatus(200); });

Checklist

  • Module generated and activated in modules_statuses.json
  • RouteServiceProvider configured with correct prefix
  • Routes defined in routes/api.php
  • Models with proper $fillable and relationships
  • Migrations created
  • FormRequests for input validation
  • Service layer for business logic
  • API Resources for JSON output
  • Controller wiring everything together
  • Tests registered in phpunit.xml
  • Tests written and passing
Last updated on