All skills
asyrafhussin avatar

/laravel-best-practices

@ef59186

Laravel 13 conventions and best practices. Use when creating controllers, models, migrations, validation, services, or structuring Laravel applications. Triggers on tasks involving Laravel architecture, Eloquent, database, API development, or PHP patterns.

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/laravel-best-practices

This session only. Nothing lands on disk.

rulescontroller-api-resources.md

≈1.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

API Resources for Response Transformation

Impact: HIGH (Consistent API responses and data transformation)

Use API Resources to transform models into consistent JSON responses.

Bad Example

// Manual array transformation in controller
class UserController extends Controller
{
    public function show(User $user)
    {
        return response()->json([
            'id' => $user->id,
            'name' => $user->name,
            'email' => $user->email,
            'avatar_url' => $user->avatar ? asset('storage/' . $user->avatar) : null,
            'created_at' => $user->created_at->toISOString(),
            // Forgot to include some fields? Inconsistent across endpoints?
        ]);
    }

    public function index()
    {
        $users = User::paginate(20);

        // Different transformation logic duplicated
        return response()->json([
            'data' => $users->map(function ($user) {
                return [
                    'id' => $user->id,
                    'name' => $user->name,
                    'email' => $user->email,
                    // Missing avatar_url? Inconsistent!
                ];
            }),
            'meta' => [
                'total' => $users->total(),
                'page' => $users->currentPage(),
            ],
        ]);
    }
}

Good Example

// app/Http/Resources/UserResource.php
namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'avatar_url' => $this->avatar
                ? asset('storage/' . $this->avatar)
                : null,
            'email_verified' => !is_null($this->email_verified_at),
            'created_at' => $this->created_at->toISOString(),
            'updated_at' => $this->updated_at->toISOString(),

            // Conditional attributes
            'is_admin' => $this->when($request->user()?->isAdmin(), $this->is_admin),

            // Include relationships when loaded
            'posts' => PostResource::collection($this->whenLoaded('posts')),
            'profile' => new ProfileResource($this->whenLoaded('profile')),

            // Counts
            'posts_count' => $this->whenCounted('posts'),

            // Pivot data
            'role' => $this->whenPivotLoaded('role_user', function () {
                return $this->pivot->role;
            }),
        ];
    }

    /**
     * Add additional data to the response.
     */
    public function with(Request $request): array
    {
        return [
            'meta' => [
                'api_version' => '1.0',
            ],
        ];
    }
}
// app/Http/Resources/UserCollection.php
namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
            'meta' => [
                'total_users' => $this->collection->count(),
                'has_admins' => $this->collection->contains('is_admin', true),
            ],
        ];
    }
}
// app/Http/Resources/PostResource.php
namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Str;

class PostResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'slug' => $this->slug,
            'excerpt' => Str::limit($this->body, 150),
            'body' => $this->when(
                $request->routeIs('posts.show'),
                $this->body
            ),
            'published_at' => $this->published_at?->toISOString(),
            'is_published' => $this->isPublished(),

            // Always include author summary
            'author' => [
                'id' => $this->author->id,
                'name' => $this->author->name,
            ],

            // Full author resource when loaded
            'author_full' => new UserResource($this->whenLoaded('author')),

            'comments' => CommentResource::collection($this->whenLoaded('comments')),
            'tags' => TagResource::collection($this->whenLoaded('tags')),

            'links' => [
                'self' => route('posts.show', $this->resource),
            ],
        ];
    }
}
// Clean API controller
namespace App\Http\Controllers;

use App\Http\Resources\UserResource;
use App\Models\User;

class UserController extends Controller
{
    public function index()
    {
        $users = User::with('profile')
            ->withCount('posts')
            ->paginate(20);

        return UserResource::collection($users);
    }

    public function show(User $user)
    {
        $user->load(['posts' => fn($q) => $q->latest()->limit(5), 'profile']);

        return new UserResource($user);
    }

    public function store(StoreUserRequest $request)
    {
        $user = User::create($request->validated());

        return new UserResource($user);
    }
}

Response structure is consistent:

{
    "data": {
        "id": 1,
        "name": "John Doe",
        "email": "john@example.com",
        "avatar_url": "https://...",
        "created_at": "2024-01-15T10:30:00.000000Z"
    },
    "meta": {
        "api_version": "1.0"
    }
}

JSON:API Resources (Laravel 13+)

Laravel 13 adds first-party JSON:API resource support for APIs that need JSON:API specification compliance. Use these when building APIs that must follow the JSON:API spec; for standard APIs, JsonResource above remains the recommended approach.

// JSON:API resources handle:
// - Resource object serialization with type/id/attributes
// - Relationship inclusion (included member)
// - Sparse fieldsets (?fields[users]=name,email)
// - JSON:API-compliant links and response headers

// Use JsonResource for most APIs.
// Use JSON:API resources only when clients require JSON:API compliance.

Why

  • Consistency: Same model always produces same JSON structure
  • Reusability: Resource used across multiple endpoints
  • Conditional data: Include data based on context
  • Relationships: Handle nested resources elegantly
  • Pagination: Automatic pagination metadata
  • API versioning: Easy to create v2 resources
  • Documentation: Resource classes document your API structure

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill is a comprehensive guide to Laravel 13 best practices, providing safe and standard architectural, database, and security patterns for PHP development. No malicious behavior or security risks were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    1/37 files flagged

  • ZeroLeaks5mo

    2 findings · Score: 80/100

Signed by skilld at ef59186. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 7 months ago
Other metadata
metadata
{
  "author": "Laravel Community",
  "version": "2.1.0",
  "laravelVersion": "13.x",
  "phpVersion": "8.3+"
}

README badge

README badge for asyrafhussin/agent-skills/laravel-best-practices