API Resource Patterns Basic Resource Structure
$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 $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 ?>api-resource-patterns
安装
npx skills add https://github.com/iserter/laravel-claude-agents --skill api-resource-patterns