api-resource-patterns

API Resource Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "api-resource-patterns" with this command: npx skills add iserter/laravel-claude-agents/iserter-laravel-claude-agents-api-resource-patterns

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

eloquent-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

laravel-tdd

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

systematic-debugging-laravel

No summary provided by upstream source.

Repository SourceNeeds Review