api-resource-patterns について
アプリケーションモデルからJSONレスポンスを構成するLaravel開発者向けのリソースパターン集です。条件付き属性、リレーション、コレクション、リンク、レスポンスのカスタマイズを例示します。
主な機能
- 01JSONリソースによるモデル変換
- 02条件付き属性とリレーションの出力
- 03リソースコレクションの構成
- 04リンクとページネーション情報の追加
- 05ステータスコードとラップ形式の変更
- 06ロード済みリレーションによるN+1クエリ回避
活用シーン
- 01モデルデータをAPIレスポンスに整形
- 02未ロードのリレーションを省略
- 03ページネーション付きコレクションの作成
- 04リンクや追加レスポンスデータの提供
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ iserter/ laravel-claude-agents --skill api-resource-patternsインストール後、Claude Code で/api-resource-patternsを入力して読み込まれたか確認します。
詳細
API リソースのパターン
基本的なリソース構造
<?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(),
];
}
}
条件付き属性
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,
]),
];
}
ネストされたリレーションシップ
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')),
];
}
リソースコレクション
<?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(),
],
];
}
}
リンクの追加
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),
],
];
}
リソースレスポンスのカスタマイズ
// 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));
}
リソース内のピボットデータ
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;
}),
];
}
ラップとラップ解除
// Disable wrapping in AppServiceProvider
use Illuminate\Http\Resources\Json\JsonResource;
public function boot()
{
JsonResource::withoutWrapping();
}
// Or per resource
public static $wrap = 'post';
追加データ付き
public function with($request): array
{
return [
'version' => '1.0.0',
'timestamp' => now()->toISOString(),
];
}
public function withResponse($request, $response)
{
$response->header('X-Value', 'True');
}
ベストプラクティス
リレーションシップには常に whenLoaded を使用する
// ✅ Prevents N+1 queries
'author' => new UserResource($this->whenLoaded('user')),
// ❌ Will cause N+1 queries
'author' => new UserResource($this->user),
型ヒントを使用する
use Illuminate\Http\Request;
public function toArray(Request $request): array
{
// ...
}
リソースの役割を明確にする
// ✅ 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 collection class
return new PostCollection(Post::paginate());
// ✅ Or collection method
return PostResource::collection(Post::all());
コントローラーでの使用
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);
}
}
チェックリスト
- リソースがモデルを一貫して変換している
- リレーションシップに whenLoaded() を使用している
- 条件付き属性に when() を使用している
- コレクションにページネーションのメタデータが含まれている
- HATEOAS のためのリンクが含まれている
- 型ヒントを使用している
- 適切な HTTP ステータスコードを使用している
- N+1 クエリが発生しない
- 日付形式が一貫している
- 適切なラップ戦略を採用している
コメント
Markdown 対応