mcpField
A
api-resource-patterns

api-resource-patterns

github.com/iserterDevelopment48110 installsUpdated 27w ago
Repository

About api-resource-patterns

A reference skill for Laravel API resource patterns, aimed at developers shaping JSON responses from application models. It demonstrates conditional fields, relationships, collections, links, and response customization.

Key features

  1. 01Transform models with JSON resources
  2. 02Include conditional attributes and relationships
  3. 03Structure resource collections
  4. 04Add links and pagination metadata
  5. 05Customize response status and wrapping
  6. 06Avoid N+1 queries with loaded relationships

Use cases

  1. 01Shape model data for API responses
  2. 02Omit relationships that are not loaded
  3. 03Build paginated collection responses
  4. 04Expose links and additional response data

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/iserter/laravel-claude-agents --skill api-resource-patterns

After installing, type/api-resource-patternsin Claude Code to confirm it loaded.

Details

From the project README

API Resource Patterns

Basic Resource Structure

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'content' => $this->content,
            'created_at' => $this->created_at->toISOString(),
            'updated_at' => $this->updated_at->toISOString(),
        ];
    }
}

Conditional Attributes

public function toArray($request): array
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        
        // Only include if loaded
        'author' => new UserResource($this->whenLoaded('user')),
        
        // Only include if condition is true
        'content' => $this->when($request->user()?->can('view', $this->resource), $this->content),
        
        // Only include if not null
        'comments_count' => $this->when($this->comments_count !== null, $this->comments_count),
        
        // Merge conditionally
        $this->mergeWhen($request->user()?->isAdmin(), [
            'internal_notes' => $this->internal_notes,
        ]),
    ];
}

Nested Relationships

public function toArray($request): array
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        
        // Single relationship
        'author' => new UserResource($this->whenLoaded('user')),
        
        // Collection relationship
        'comments' => CommentResource::collection($this->whenLoaded('comments')),
        
        // Nested relationships
        'category' => new CategoryResource($this->whenLoaded('category')),
    ];
}

Resource Collections

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class PostCollection extends ResourceCollection
{
    public function toArray($request): array
    {
        return [
            'data' => $this->collection,
            'meta' => [
                'total' => $this->total(),
                'count' => $this->count(),
                'per_page' => $this->perPage(),
                'current_page' => $this->currentPage(),
                'total_pages' => $this->lastPage(),
            ],
            'links' => [
                'self' => $request->url(),
                'first' => $this->url(1),
                'last' => $this->url($this->lastPage()),
                'prev' => $this->previousPageUrl(),
                'next' => $this->nextPageUrl(),
            ],
        ];
    }
}

Adding Links

public function toArray($request): array
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        'links' => [
            'self' => route('posts.show', $this->id),
            'author' => route('users.show', $this->user_id),
            'comments' => route('posts.comments.index', $this->id),
        ],
    ];
}

Resource Response Customization

// In controller
public function store(Request $request)
{
    $post = Post::create($request->validated());
    
    return (new PostResource($post))
        ->response()
        ->setStatusCode(201)
        ->header('Location', route('posts.show', $post));
}

Pivot Data in Resources

public function toArray($request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'assigned_at' => $this->whenPivotLoaded('role_user', function () {
            return $this->pivot->created_at;
        }),
        'expires_at' => $this->whenPivotLoadedAs('assignment', 'role_user', function () {
            return $this->assignment->expires_at;
        }),
    ];
}

Wrapping and Unwrapping

// Disable wrapping in AppServiceProvider
use Illuminate\Http\Resources\Json\JsonResource;

public function boot()
{
    JsonResource::withoutWrapping();
}

// Or per resource
public static $wrap = 'post';

With Additional Data

public function with($request): array
{
    return [
        'version' => '1.0.0',
        'timestamp' => now()->toISOString(),
    ];
}

public function withResponse($request, $response)
{
    $response->header('X-Value', 'True');
}

Best Practices

Always Use whenLoaded for Relationships
// ✅ Prevents N+1 queries
'author' => new UserResource($this->whenLoaded('user')),

// ❌ Will cause N+1 queries
'author' => new UserResource($this->user),
Use Type Hints
use Illuminate\Http\Request;

public function toArray(Request $request): array
{
    // ...
}
Keep Resources Focused
// ✅ Create separate resources for different contexts
class PostResource extends JsonResource { }
class PostListResource extends JsonResource { }
class PostDetailResource extends JsonResource { }

// ❌ Don't make one resource do everything
Use Resource Collections
// ✅ Use collection class
return new PostCollection(Post::paginate());

// ✅ Or collection method
return PostResource::collection(Post::all());

Controller Usage

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::with(['user', 'category'])
            ->withCount('comments')
            ->paginate(15);
        
        return new PostCollection($posts);
    }
    
    public function show(Post $post)
    {
        $post->load(['user', 'comments.user', 'tags']);
        
        return new PostResource($post);
    }
    
    public function store(StorePostRequest $request)
    {
        $post = Post::create($request->validated());
        
        return (new PostResource($post))
            ->response()
            ->setStatusCode(201);
    }
}

Checklist

  • Resources transform models consistently
  • Relationships loaded with whenLoaded()
  • Conditional attributes use when()
  • Collections include pagination metadata
  • Links included for HATEOAS
  • Type hints used
  • Proper HTTP status codes
  • No N+1 queries
  • Consistent date formatting
  • Appropriate wrapping strategy

Comments

Markdown supported