agentleFS
JustSteveKingJustSteveKing/laravel-api-skill/laravel-api/SKILL.md

laravel-api

Skill27 starsChanged 10 months agoMITView on GitHub

Build production-grade Laravel REST APIs using opinionated architecture patterns with Laravel best practices. Use when building, scaffoling, or reviewing Laravel APIs with specifications for stateless design, versioned endpoints, invokable controllers, Form Request DTOs, Action classes, JWT authentication, and PSR-12 code quality standards. Triggers on "build a Laravel API", "create Laravel endpoints", "add API authentication", "review Laravel API code", "refactor Laravel API", or "improve Laravel code quality".

2 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions2 projects use a version of this.

    Word for word · 1

    1. noartem/skills★ 13100% same

    Edited · 0

    None found.

    Wrote their own version · 1

    1. STIFLEUR390/agent-skills★ 0same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

The file

Show its rules and text

We split this skill into the rules it gives an agent

23 rules. About 0 are common, 19 are distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈19

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When user requests a Laravel API, follow this workflow:

    line 12
  2. Distinctive:

    Iterate on remaining resources: - Follow established pattern

    line 18
  3. Very specific:

    Read references/architecture.md for comprehensive details.

    line 22
  4. Distinctive:

    Stateless by design: - No hidden dependencies, explicit data flow

    line 24
  5. Distinctive:

    All code must follow Laravel best practices and PSR-12 standards:

    line 31
  6. Distinctive:

    Explicit Over Implicit: - Prefer clear, readable code over clever shortcuts

    line 34
  7. Distinctive:

    Type Declarations: - Always use return types on methods, parameter types where beneficial

    line 35
  8. Distinctive:

    Avoid Nested Ternaries: - Use match expressions, switch, or if/else for clarity

    line 36
  9. Distinctive:

    Consistent Naming: - Follow PSR-12 and Laravel conventions strictly

    line 37
  10. Distinctive:

    Remove unnecessary complexity while preserving exact behavior

    line 44
  11. Distinctive:

    Always use ULIDs. Keep models simple - data access only.

    line 82
  12. Very specific:

    Create resource route file at routes/api/{resource}.php:

    line 122
  13. Very specific:

    Create invokable controller at app/Http/Controllers/{Resource}/V1/{Operation}Controller.php:

    line 245
  14. Distinctive:

    Use Spatie Query Builder for filtering, sorting, includes:

    line 304
  15. Very specific:

    Create V2 namespace: App\Http\Controllers\Tasks\V2\

    line 320
  16. Distinctive:

    Add V2 route group in resource file

    line 321
  17. Distinctive:

    Add Sunset middleware to V1 routes:

    line 322
  18. Distinctive:

    Use PHP Open Source Saver JWT package:

    line 332
  19. Distinctive:

    When reviewing or refactoring Laravel API code, apply these principles:

    line 388
  20. Distinctive:

    Preserve Functionality: - Ensure refactorings don't change behavior

    line 392
  21. Distinctive:

    Check Type Safety: - Add missing return types and parameter types

    line 393
  22. Distinctive:

    Verify Standards: - Ensure PSR-12 compliance with declare(strict_types=1)

    line 396
  23. Distinctive:

    Improve Naming: - Use descriptive names that reveal intent

    line 397

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
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

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

final class Task extends Model
{
    use HasFactory;
    use HasUlids;
    
    protected $fillable = [
        'title',
        'description',
        'status',
        'project_id',
    ];

    protected $casts = [
        'created_at' => '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:

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:

Route::prefix('v1')->group(function () {
    require __DIR__ . '/tasks.php';
});

Step 3: DTO (Payload)

Create at app/Http/Payloads/{Resource}/{Operation}Payload.php:

<?php

declare(strict_types=1);

namespace App\Http\Payloads\Tasks;

final readonly class StoreTaskPayload
{
    public function __construct(
        public string $title,
        public ?string $description,
        public string $status,
        public string $projectId,
    ) {}

    public function toArray(): array
    {
        return [
            'title' => $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

declare(strict_types=1);

namespace App\Http\Requests\Tasks\V1;

use App\Http\Payloads\Tasks\StoreTaskPayload;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

final class StoreTaskRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'title' => ['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

declare(strict_types=1);

namespace App\Actions\Tasks;

use App\Http\Payloads\Tasks\StoreTaskPayload;
use App\Models\Task;

final readonly class CreateTask
{
    public function handle(StoreTaskPayload $payload): Task
    {
        return Task::create($payload->toArray());
    }
}

Step 6: Controller

Create invokable controller at app/Http/Controllers/{Resource}/V1/{Operation}Controller.php:

<?php

declare(strict_types=1);

namespace App\Http\Controllers\Tasks\V1;

use App\Actions\Tasks\CreateTask;
use App\Http\Requests\Tasks\V1\StoreTaskRequest;
use App\Http\Responses\JsonDataResponse;
use Illuminate\Http\JsonResponse;

final readonly class StoreController
{
    public function __construct(
        private CreateTask $createTask,
    ) {}

    public function __invoke(StoreTaskRequest $request): JsonResponse
    {
        $task = $this->createTask->handle(
            payload: $request->payload(),
        );

        return new JsonDataResponse(
            data: $task,
            status: 201,
        );
    }
}

Response Format

Standard format for all responses:

Success:

{
    "data": {...},
    "meta": {...}
}

Error (Problem+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:

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:
Route::middleware(['auth:api', 'http.sunset:2025-12-31'])->group(function () {
    // V1 routes
});

Authentication Setup

Use PHP Open Source Saver JWT package:

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:

'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

Essential Setup

Add to app/Providers/AppServiceProvider.php:

use Illuminate\Database\Eloquent\Model;

public function boot(): void
{
    Model::shouldBeStrict(); // Prevent N+1 queries
}

Register HttpSunset middleware in app/Http/Kernel.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:

// ❌ 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

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.