Claude-skill-registry api-resource-patterns
Best practices for Laravel API Resources including resource transformation, collection handling, conditional attributes, and relationship loading.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/api-resource-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-api-resource-patterns && rm -rf "$T"
manifest:
skills/data/api-resource-patterns/SKILL.mdsource content
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