--- id: JustSteveKing/laravel-api-skill/laravel-api version: "372a821d" license: MIT install: manual updated: 2026-01-12 --- # laravel-api — Laravel API guides you through building REST APIs using stateless, resource-scoped architecture with explicit boundaries between HTTP, business logic, and data layers. It enforces PSR-12 code quality, JWT authentication, versioned endpoints, and invokable controllers to establish maintainable patterns from your first resource. Publisher: JustSteveKing · Stars: 26 · Updated: 2026-01-12 Install (manual): `git clone https://github.com/JustSteveKing/laravel-api-skill` ## SKILL.md # Laravel API - Steve's Architecture Build Laravel REST APIs with clean, stateless, resource-scoped architecture. ## Quick Start When user requests a Laravel API, follow this workflow: 1. **Understand requirements** - What resources? What operations? Authentication needed? 2. **Initialize project structure** - Set up routing, remove frontend bloat 3. **Build first resource** - Complete CRUD to establish pattern 4. **Add authentication** - JWT via PHP Open Source Saver 5. **Iterate on remaining resources** - Follow established pattern ## Core Architecture Principles Read `references/architecture.md` for comprehensive details. Key principles: 1. **Stateless by design** - No hidden dependencies, explicit data flow 2. **Boundary-first** - Clear separation of HTTP, business logic, data layers 3. **Resource-scoped** - Routes, controllers organized by resource 4. **Version discipline** - Namespace-based versioning, HTTP Sunset headers ## Code Quality Standards All code must follow Laravel best practices and PSR-12 standards: 1. **Preserve Functionality** - Refactorings change HOW code works, never WHAT it does 2. **Explicit Over Implicit** - Prefer clear, readable code over clever shortcuts 3. **Type Declarations** - Always use return types on methods, parameter types where beneficial 4. **Avoid Nested Ternaries** - Use match expressions, switch, or if/else for clarity 5. **Consistent Naming** - Follow PSR-12 and Laravel conventions strictly 6. **Proper Namespacing** - Organize imports logically, use full type hints **When reviewing or refactoring code:** - Focus on clarity and maintainability over cleverness - Simplify complex nested logic into readable structures - Extract magic values into named constants or config - Remove unnecessary complexity while preserving exact behavior ## Project Structure ``` routes/api/ routes.php # Main entry point, version grouping tasks.php # All task routes, all versions projects.php # All project routes, all versions app/Http/ Controllers/{Resource}/V1/ StoreController.php # Always invokable IndexController.php ShowController.php Requests/{Resource}/V1/ StoreTaskRequest.php # Validation + payload() method Payloads/{Resource}/ StoreTaskPayload.php # Simple DTOs with toArray() Responses/ JsonDataResponse.php # Implements Responsable JsonErrorResponse.php Middleware/ HttpSunset.php app/Actions/{Resource}/ CreateTask.php # Single-purpose business logic app/Services/ # Only when logic too complex for Actions app/Models/ Task.php # HasUlids trait, simple data access ``` ## Building a New Resource Endpoint ### Step 1: Model Always use ULIDs. Keep models simple - data access only. ```php 'datetime', 'updated_at' => 'datetime', ]; public function project(): BelongsTo { return $this->belongsTo(Project::class); } } ``` ### Step 2: Routes Create resource route file at `routes/api/{resource}.php`: ```php use App\Http\Controllers\Tasks\V1; Route::middleware(['auth:api'])->group(function () { Route::get('/tasks', V1\IndexController::class); Route::post('/tasks', V1\StoreController::class); Route::get('/tasks/{task}', V1\ShowController::class); Route::patch('/tasks/{task}', V1\UpdateController::class); Route::delete('/tasks/{task}', V1\DestroyController::class); }); ``` Include in `routes/api/routes.php`: ```php Route::prefix('v1')->group(function () { require __DIR__ . '/tasks.php'; }); ``` ### Step 3: DTO (Payload) Create at `app/Http/Payloads/{Resource}/{Operation}Payload.php`: ```php $this->title, 'description' => $this->description, 'status' => $this->status, 'project_id' => $this->projectId, ]; } } ``` ### Step 4: Form Request Create at `app/Http/Requests/{Resource}/V1/{Operation}Request.php`: ```php ['required', 'string', 'max:255'], 'description' => ['nullable', 'string', 'max:1000'], 'status' => ['required', Rule::in(['pending', 'in_progress', 'completed'])], 'project_id' => ['required', 'string', 'exists:projects,id'], ]; } public function payload(): StoreTaskPayload { return new StoreTaskPayload( title: $this->string('title')->toString(), description: $this->string('description')->toString(), status: $this->string('status')->toString(), projectId: $this->string('project_id')->toString(), ); } } ``` ### Step 5: Action Create at `app/Actions/{Resource}/{Operation}.php`: ```php toArray()); } } ``` ### Step 6: Controller Create invokable controller at `app/Http/Controllers/{Resource}/V1/{Operation}Controller.php`: ```php createTask->handle( payload: $request->payload(), ); return new JsonDataResponse( data: $task, status: 201, ); } } ``` ## Response Format Standard format for all responses: **Success:** ```json { "data": {...}, "meta": {...} } ``` **Error (Problem+JSON):** ```json { "type": "about:blank", "title": "Validation Failed", "status": 422, "detail": "The given data was invalid", "errors": {...} } ``` ## Query Building Use Spatie Query Builder for filtering, sorting, includes: ```php use Spatie\QueryBuilder\QueryBuilder; $tasks = QueryBuilder::for(Task::class) ->allowedFilters(['status', 'priority']) ->allowedSorts(['created_at', 'due_date']) ->allowedIncludes(['project', 'assignee']) ->paginate(); ``` ## Versioning Endpoints When creating V2: 1. Create V2 namespace: `App\Http\Controllers\Tasks\V2\` 2. Add V2 route group in resource file 3. Add Sunset middleware to V1 routes: ```php Route::middleware(['auth:api', 'http.sunset:2025-12-31'])->group(function () { // V1 routes }); ``` ## Authentication Setup Use PHP Open Source Saver JWT package: ```bash composer require php-open-source-saver/jwt-auth php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider" php artisan jwt:secret ``` Configure in `config/auth.php`: ```php 'guards' => [ 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ], ``` ## Essential Setup Add to `app/Providers/AppServiceProvider.php`: ```php use Illuminate\Database\Eloquent\Model; public function boot(): void { Model::shouldBeStrict(); // Prevent N+1 queries } ``` Register HttpSunset middleware in `app/Http/Kernel.php`: ```php protected $middlewareAliases = [ 'http.sunset' => \App\Http\Middleware\HttpSunset::class, ]; ``` ## Anti-Patterns to Avoid - Using auto-increment IDs instead of ULIDs - Business logic in models - Multiple actions per controller - Accessing request data directly in controllers/actions - Hidden query scopes - Service classes when an Action would suffice - Breaking changes without versioning - Inconsistent response formats - Nested ternary operators (use match expressions instead) - Missing type declarations on methods and parameters - Overly compact "clever" code that sacrifices readability ## Code Review & Refactoring When reviewing or refactoring Laravel API code, apply these principles: ### Simplification Checklist 1. **Preserve Functionality** - Ensure refactorings don't change behavior 2. **Check Type Safety** - Add missing return types and parameter types 3. **Simplify Logic** - Replace nested ternaries with match expressions 4. **Extract Complexity** - Move complex conditions into named methods 5. **Verify Standards** - Ensure PSR-12 compliance with declare(strict_types=1) 6. **Improve Naming** - Use descriptive names that reveal intent ### Match Expression Pattern Replace nested ternaries with match for clarity: ```php // ❌ Avoid: Nested ternary $status = $task->completed_at ? ($task->verified ? 'verified' : 'completed') : ($task->started_at ? 'in_progress' : 'pending'); // ✅ Prefer: Match expression $status = match (true) { $task->completed_at && $task->verified => 'verified', $task->completed_at => 'completed', $task->started_at => 'in_progress', default => 'pending', }; ``` ## References - **architecture.md** - Comprehensive architectural patterns and principles - **code-examples.md** - Complete working examples for every component - **code-quality.md** - Laravel best practices, refactoring patterns, and PSR-12 standards ## Templates Template files in `assets/templates/` for quick scaffolding: - Controller.php - FormRequest.php - Payload.php - Action.php - Model.php [View on SkillFed](https://skillfed.io/JustSteveKing/laravel-api-skill/laravel-api) · [View on GitHub](https://github.com/JustSteveKing/laravel-api-skill)