{"id":295,"date":"2025-08-27T19:23:10","date_gmt":"2025-08-27T19:23:10","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=295"},"modified":"2025-08-27T19:23:12","modified_gmt":"2025-08-27T19:23:12","slug":"how-to-use-eloquent-api-resources-for-clean-apis","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/how-to-use-eloquent-api-resources-for-clean-apis\/","title":{"rendered":"How to Use Eloquent API Resources for Clean APIs"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\"><strong>How to Use Eloquent API Resources for Clean APIs<\/strong><\/h2>\n\n\n\n<p>Eloquent API Resources give you a clean, explicit layer to shape JSON responses. Instead of returning raw models (which may leak internal fields), resources let you control fields, nest relations, add meta and links, and keep your API consistent. In this guide, you\u2019ll build resources for posts and users, paginate collections, conditionally include attributes and relations, and add meta information\u2014then consume the API from a simple UI.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1 &#8211; Create Models (Sample Domain)<\/strong><\/h2>\n\n\n\n<p>We\u2019ll use a simple domain: <code>Post<\/code> belongs to <code>User<\/code>. If you already have these, skip ahead.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">php artisan make:model Post -mf<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This creates the model, a migration, and a factory. You\u2019ll use them to scaffold data for testing the API responses.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ database\/migrations\/xxxx_xx_xx_create_posts_table.php<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Migrations<\/span>\\<span class=\"hljs-title\">Migration<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Schema<\/span>\\<span class=\"hljs-title\">Blueprint<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Schema<\/span>;\n\n<span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Migration<\/span> <\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">up<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">void<\/span> <\/span>{\n        Schema::create(<span class=\"hljs-string\">'posts'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Blueprint $table)<\/span> <\/span>{\n            $table-&gt;id();\n            $table-&gt;foreignId(<span class=\"hljs-string\">'user_id'<\/span>)-&gt;constrained()-&gt;cascadeOnDelete();\n            $table-&gt;string(<span class=\"hljs-string\">'title'<\/span>);\n            $table-&gt;text(<span class=\"hljs-string\">'body'<\/span>);\n            $table-&gt;json(<span class=\"hljs-string\">'meta'<\/span>)-&gt;nullable(); <span class=\"hljs-comment\">\/\/ tags, reading_time, etc.<\/span>\n            $table-&gt;timestamps();\n        });\n    }\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">down<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">void<\/span> <\/span>{\n        Schema::dropIfExists(<span class=\"hljs-string\">'posts'<\/span>);\n    }\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The migration includes a nullable JSON <code>meta<\/code> field to demonstrate how resources can shape nested JSON cleanly.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2 &#8211; Generate API Resources<\/strong><\/h2>\n\n\n\n<p>Create resources for <code>Post<\/code> and <code>User<\/code>. These classes transform models to JSON.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">php artisan make:resource PostResource\nphp artisan make:resource UserResource<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Resources live in <code>app\/Http\/Resources<\/code>. Each implements a <code>toArray($request)<\/code> method that returns the exact JSON structure you want to expose.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Resources\/UserResource.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Resources<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Resources<\/span>\\<span class=\"hljs-title\">Json<\/span>\\<span class=\"hljs-title\">JsonResource<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">UserResource<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">JsonResource<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">toArray<\/span><span class=\"hljs-params\">($request)<\/span>: <span class=\"hljs-title\">array<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> &#91;\n            <span class=\"hljs-string\">'id'<\/span>    =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;id,\n            <span class=\"hljs-string\">'name'<\/span>  =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;name,\n            <span class=\"hljs-string\">'email'<\/span> =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;email,\n        ];\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This keeps the user payload minimal and explicit. If you don\u2019t want to expose emails publicly, omit or conditionally include it later.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Resources\/PostResource.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Resources<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Resources<\/span>\\<span class=\"hljs-title\">Json<\/span>\\<span class=\"hljs-title\">JsonResource<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">PostResource<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">JsonResource<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">toArray<\/span><span class=\"hljs-params\">($request)<\/span>: <span class=\"hljs-title\">array<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> &#91;\n            <span class=\"hljs-string\">'id'<\/span>      =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;id,\n            <span class=\"hljs-string\">'title'<\/span>   =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;title,\n            <span class=\"hljs-string\">'excerpt'<\/span> =&gt; str(<span class=\"hljs-keyword\">$this<\/span>-&gt;body)-&gt;limit(<span class=\"hljs-number\">140<\/span>),\n            <span class=\"hljs-string\">'body'<\/span>    =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;when($request-&gt;routeIs(<span class=\"hljs-string\">'posts.show'<\/span>), <span class=\"hljs-keyword\">$this<\/span>-&gt;body),\n            <span class=\"hljs-string\">'meta'<\/span>    =&gt; &#91;\n                <span class=\"hljs-string\">'tags'<\/span>         =&gt; data_get(<span class=\"hljs-keyword\">$this<\/span>-&gt;meta, <span class=\"hljs-string\">'tags'<\/span>, &#91;]),\n                <span class=\"hljs-string\">'reading_time'<\/span> =&gt; data_get(<span class=\"hljs-keyword\">$this<\/span>-&gt;meta, <span class=\"hljs-string\">'reading_time'<\/span>),\n            ],\n            <span class=\"hljs-string\">'author'<\/span>  =&gt; <span class=\"hljs-keyword\">new<\/span> UserResource(<span class=\"hljs-keyword\">$this<\/span>-&gt;whenLoaded(<span class=\"hljs-string\">'user'<\/span>)),\n            <span class=\"hljs-string\">'created_at'<\/span> =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;created_at?-&gt;toIso8601String(),\n            <span class=\"hljs-string\">'updated_at'<\/span> =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;updated_at?-&gt;toIso8601String(),\n            <span class=\"hljs-comment\">\/\/ Example link<\/span>\n            <span class=\"hljs-string\">'links'<\/span> =&gt; &#91;\n                <span class=\"hljs-string\">'self'<\/span> =&gt; route(<span class=\"hljs-string\">'posts.show'<\/span>, <span class=\"hljs-keyword\">$this<\/span>-&gt;id),\n            ],\n        ];\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This resource exposes a short <code>excerpt<\/code> for lists and includes the full <code>body<\/code> only on the show route using <code>when()<\/code>. The <code>author<\/code> is wrapped in <code>UserResource<\/code> and included only if the relation is eager-loaded (<code>whenLoaded('user')<\/code>).<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3 &#8211; Controller: Return Resources and Collections<\/strong><\/h2>\n\n\n\n<p>Use resources in controller methods. For collections, either use <code>PostResource::collection($posts)<\/code> or make a separate <code>PostCollection<\/code>. Here we\u2019ll keep it simple.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Controllers\/PostController.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Resources<\/span>\\<span class=\"hljs-title\">PostResource<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">Post<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Request<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">PostController<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Controller<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">index<\/span><span class=\"hljs-params\">(Request $request)<\/span>\n    <\/span>{\n        $posts = Post::with(<span class=\"hljs-string\">'user'<\/span>)\n            -&gt;latest()\n            -&gt;paginate(<span class=\"hljs-number\">10<\/span>);\n\n        <span class=\"hljs-keyword\">return<\/span> PostResource::collection($posts)\n            -&gt;additional(&#91;\n                <span class=\"hljs-string\">'meta'<\/span> =&gt; &#91;\n                    <span class=\"hljs-string\">'copyright'<\/span> =&gt; <span class=\"hljs-string\">'\u00a9 Your Company'<\/span>,\n                ],\n            ]);\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">show<\/span><span class=\"hljs-params\">(Post $post)<\/span>\n    <\/span>{\n        $post-&gt;load(<span class=\"hljs-string\">'user'<\/span>);\n\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> PostResource($post);\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">store<\/span><span class=\"hljs-params\">(Request $request)<\/span>\n    <\/span>{\n        $data = $request-&gt;validate(&#91;\n            <span class=\"hljs-string\">'title'<\/span> =&gt; &#91;<span class=\"hljs-string\">'required'<\/span>,<span class=\"hljs-string\">'string'<\/span>,<span class=\"hljs-string\">'max:150'<\/span>],\n            <span class=\"hljs-string\">'body'<\/span>  =&gt; &#91;<span class=\"hljs-string\">'required'<\/span>,<span class=\"hljs-string\">'string'<\/span>],\n            <span class=\"hljs-string\">'meta'<\/span>  =&gt; &#91;<span class=\"hljs-string\">'nullable'<\/span>,<span class=\"hljs-string\">'array'<\/span>],\n        ]);\n\n        $post = Post::create($data + &#91;<span class=\"hljs-string\">'user_id'<\/span> =&gt; $request-&gt;user()-&gt;id]);\n\n        <span class=\"hljs-keyword\">return<\/span> (<span class=\"hljs-keyword\">new<\/span> PostResource($post-&gt;load(<span class=\"hljs-string\">'user'<\/span>)))\n            -&gt;response()\n            -&gt;setStatusCode(<span class=\"hljs-number\">201<\/span>);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code>index()<\/code> returns a paginated collection wrapped by <code>PostResource<\/code>, and <code>additional()<\/code> appends custom top-level <code>meta<\/code>. <code>show()<\/code> returns a single resource. <code>store()<\/code> validates input, creates the post, and returns the serialized resource with status 201.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4 &#8211; API Routes and Pagination Shape<\/strong><\/h2>\n\n\n\n<p>Define API routes and see how pagination metadata\/links are included automatically when you pass a LengthAwarePaginator into <code>::collection()<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ routes\/api.php<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">PostController<\/span>;\n\nRoute::get(<span class=\"hljs-string\">'\/posts'<\/span>, &#91;PostController::class, <span class=\"hljs-string\">'index'<\/span>])-&gt;name(<span class=\"hljs-string\">'posts.index'<\/span>);\nRoute::get(<span class=\"hljs-string\">'\/posts\/{post}'<\/span>, &#91;PostController::class, <span class=\"hljs-string\">'show'<\/span>])-&gt;name(<span class=\"hljs-string\">'posts.show'<\/span>);\nRoute::middleware(<span class=\"hljs-string\">'auth:sanctum'<\/span>)-&gt;post(<span class=\"hljs-string\">'\/posts'<\/span>, &#91;PostController::class, <span class=\"hljs-string\">'store'<\/span>])-&gt;name(<span class=\"hljs-string\">'posts.store'<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Unprotected <code>GET<\/code> routes serve public data. The <code>POST<\/code> route requires authentication (e.g., Sanctum), keeping write operations secured.<\/p>\n\n\n\n<p>If you don\u2019t want the default wrapping key (<code>data<\/code>) around your resource payloads, you can disable it globally:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Providers\/AppServiceProvider.php (boot)<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Resources<\/span>\\<span class=\"hljs-title\">Json<\/span>\\<span class=\"hljs-title\">JsonResource<\/span>;\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">boot<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">void<\/span>\n<\/span>{\n    JsonResource::withoutWrapping();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>With wrapping disabled, the collection returns an array at the top level alongside <code>links<\/code> and <code>meta<\/code> for pagination. Choose the style your clients expect.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5 &#8211; Conditional Attributes and Merged Blocks<\/strong><\/h2>\n\n\n\n<p>Use helpers like <code>when()<\/code>, <code>mergeWhen()<\/code>, and <code>whenLoaded()<\/code> to keep responses compact and context-aware.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Resources\/PostResource.php (snippets)<\/span>\n<span class=\"hljs-keyword\">return<\/span> &#91;\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n    <span class=\"hljs-string\">'is_owner'<\/span> =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;when(\n        optional($request-&gt;user())?-&gt;id === <span class=\"hljs-keyword\">$this<\/span>-&gt;user_id,\n        <span class=\"hljs-keyword\">true<\/span>\n    ),\n\n    <span class=\"hljs-keyword\">$this<\/span>-&gt;mergeWhen($request-&gt;routeIs(<span class=\"hljs-string\">'posts.show'<\/span>), &#91;\n        <span class=\"hljs-string\">'full_body'<\/span> =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;body,\n        <span class=\"hljs-string\">'meta'<\/span>      =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;meta,\n    ]),\n\n    <span class=\"hljs-string\">'author'<\/span> =&gt; <span class=\"hljs-keyword\">new<\/span> UserResource(<span class=\"hljs-keyword\">$this<\/span>-&gt;whenLoaded(<span class=\"hljs-string\">'user'<\/span>)),\n];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code>when()<\/code> includes fields only under certain conditions (e.g., current user is the owner). <code>mergeWhen()<\/code> adds a block of fields for detail views without bloating list responses. <code>whenLoaded()<\/code> safely includes relations if they were eager-loaded.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>6 &#8211; Eager Loading and N+1 Safety<\/strong><\/h2>\n\n\n\n<p>Resources don\u2019t fetch relations; they only serialize what you loaded. Always eager-load relations in controllers to avoid N+1 queries.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ In PostController@index<\/span>\n$posts = Post::with(<span class=\"hljs-string\">'user'<\/span>)-&gt;latest()-&gt;paginate(<span class=\"hljs-number\">10<\/span>);\n<span class=\"hljs-keyword\">return<\/span> PostResource::collection($posts);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Because <code>user<\/code> is loaded on the query, <code>UserResource<\/code> in <code>PostResource<\/code> can serialize the <code>author<\/code> efficiently.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>7 &#8211; Adding Top-Level Meta and Custom Status Codes<\/strong><\/h2>\n\n\n\n<p>Return HTTP status codes using the <code>response()<\/code> helper, and add top-level <code>meta<\/code> or <code>links<\/code> when needed.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ Example in store()<\/span>\n<span class=\"hljs-keyword\">return<\/span> (<span class=\"hljs-keyword\">new<\/span> PostResource($post-&gt;load(<span class=\"hljs-string\">'user'<\/span>)))\n    -&gt;additional(&#91;<span class=\"hljs-string\">'meta'<\/span> =&gt; &#91;<span class=\"hljs-string\">'created'<\/span> =&gt; <span class=\"hljs-keyword\">true<\/span>]])\n    -&gt;response()\n    -&gt;setStatusCode(<span class=\"hljs-number\">201<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This returns the serialized post with a <code>201 Created<\/code> status and a <code>meta.created<\/code> flag, which is handy for clients.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>8 &#8211; UI: Consuming the API from a Blade Page<\/strong><\/h2>\n\n\n\n<p>Here\u2019s a minimal UI that fetches <code>\/api\/posts<\/code> and renders a list. You can adapt this to your marketing site or dashboard.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">&lt;!-- resources\/views\/posts\/index.blade.php --&gt;\n@extends(<span class=\"hljs-string\">'layouts.app'<\/span>)\n\n@section(<span class=\"hljs-string\">'content'<\/span>)\n&lt;div <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">container<\/span>\"&gt;\n  &lt;<span class=\"hljs-title\">h1<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">mb<\/span>-4\"&gt;<span class=\"hljs-title\">Latest<\/span> <span class=\"hljs-title\">Posts<\/span>&lt;\/<span class=\"hljs-title\">h1<\/span>&gt;\n  &lt;<span class=\"hljs-title\">div<\/span> <span class=\"hljs-title\">id<\/span>=\"<span class=\"hljs-title\">post<\/span>-<span class=\"hljs-title\">list<\/span>\"&gt;<span class=\"hljs-title\">Loading<\/span>...&lt;\/<span class=\"hljs-title\">div<\/span>&gt;\n&lt;\/<span class=\"hljs-title\">div<\/span>&gt;\n\n@<span class=\"hljs-title\">push<\/span>('<span class=\"hljs-title\">scripts<\/span>')\n&lt;<span class=\"hljs-title\">script<\/span>&gt;\n  <span class=\"hljs-title\">fetch<\/span>('\/<span class=\"hljs-title\">api<\/span>\/<span class=\"hljs-title\">posts<\/span>')\n    .<span class=\"hljs-title\">then<\/span>(<span class=\"hljs-title\">r<\/span> =&gt; <span class=\"hljs-title\">r<\/span>.<span class=\"hljs-title\">json<\/span>())\n    .<span class=\"hljs-title\">then<\/span>(<span class=\"hljs-title\">json<\/span> =&gt; <\/span>{\n      <span class=\"hljs-keyword\">const<\/span> data = json.data ?? json; <span class=\"hljs-comment\">\/\/ supports wrapped or unwrapped<\/span>\n      <span class=\"hljs-keyword\">const<\/span> <span class=\"hljs-keyword\">list<\/span> = document.getElementById(<span class=\"hljs-string\">'post-list'<\/span>);\n      <span class=\"hljs-keyword\">list<\/span>.innerHTML = <span class=\"hljs-string\">''<\/span>;\n      data.<span class=\"hljs-keyword\">forEach<\/span>(p =&gt; {\n        <span class=\"hljs-keyword\">const<\/span> card = document.createElement(<span class=\"hljs-string\">'div'<\/span>);\n        card.className = <span class=\"hljs-string\">'card mb-3'<\/span>;\n        card.innerHTML = `\n          &lt;div <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">card<\/span>-<span class=\"hljs-title\">body<\/span>\"&gt;\n            &lt;<span class=\"hljs-title\">h5<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">card<\/span>-<span class=\"hljs-title\">title<\/span>\"&gt;$<\/span>{p.title}&lt;\/h5&gt;\n            &lt;p <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">card<\/span>-<span class=\"hljs-title\">text<\/span>\"&gt;$<\/span>{p.excerpt}&lt;\/p&gt;\n            &lt;a href=<span class=\"hljs-string\">\"\/posts\/${p.id}\"<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">btn<\/span> <span class=\"hljs-title\">btn<\/span>-<span class=\"hljs-title\">theme<\/span> <span class=\"hljs-title\">r<\/span>-04\"&gt;<span class=\"hljs-title\">Read<\/span>&lt;\/<span class=\"hljs-title\">a<\/span>&gt;\n          &lt;\/<span class=\"hljs-title\">div<\/span>&gt;`;\n        <span class=\"hljs-title\">list<\/span>.<span class=\"hljs-title\">appendChild<\/span>(<span class=\"hljs-title\">card<\/span>);\n      });\n    });\n&lt;\/<span class=\"hljs-title\">script<\/span>&gt;\n@<span class=\"hljs-title\">endpush<\/span>\n@<span class=\"hljs-title\">endsection<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The UI calls your API, supports either wrapped (<code>{ data: [...] }<\/code>) or unwrapped collection payloads, and renders cards with title and excerpt from the resource.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>Eloquent API Resources give you a declarative way to shape JSON: pick fields, conditionally include details, nest relations, add meta, and keep pagination consistent. Keep controllers focused on queries and authorization, and let resources handle presentation of data for clients. This makes for predictable, maintainable APIs that evolve gracefully.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\">What\u2019s Next<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/blog\/soft-deletes-in-laravel-restore-force-delete-and-prune-data\">Soft Deletes: Restore, Force Delete, and Prune Data<\/a><\/li>\n<li><a href=\"\/blog\/handling-large-data-sets-in-laravel-with-chunking-cursors\">Handling Large Data Sets with Chunking &amp; Cursors<\/a><\/li>\n<li><a href=\"\/blog\/filtering-and-searching-with-laravel-eloquent-query-builder\">Filtering and Searching with Eloquent Query Builder<\/a><\/li>\n<\/ul>\n\n","protected":false},"excerpt":{"rendered":"<p>How to Use Eloquent API Resources for Clean APIs Eloquent API Resources give you a clean, explicit layer to shape JSON responses. Instead of returning raw models (which may leak internal fields), resources let you control fields, nest relations, add meta and links, and keep your API consistent. In this guide, you\u2019ll build resources for [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":299,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[38,36,37],"class_list":["post-295","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-database","tag-eloquent","tag-relationships"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/295","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/comments?post=295"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/295\/revisions"}],"predecessor-version":[{"id":298,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/295\/revisions\/298"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/299"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=295"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=295"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=295"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}