Hello! Welcome back to our module on API Development.
In the last lesson, we covered how to use Resource Collections to format lists of models, including how to handle paginated data and add custom metadata. This gave you the tools to create clean, structured responses for endpoints that return multiple items.
So far, the structure of our resource response has been static. Every time we use a UserResource, for example, it returns the same set of fields. But what if we want to build more dynamic and intelligent APIs? What if a user's email should only be visible to an administrator? Or what if we want to avoid performance issues by only including a model's relationships when they're actually needed?
This is where conditional attributes and relationships come in. Today, we'll dive into the methods that give you fine-grained control over your API responses, making them more secure, efficient, and flexible.
Our learning outcome for this lesson is to conditionally include attributes and relationships in API resources.
1. Conditional Attributes
Let's start with individual fields within a resource. You often need to include an attribute only if a certain condition is met. Laravel's API resources provide several helper methods for this, so you don't have to clutter your toArray method with if statements.
The when() Method
The most common helper is when(). It takes two arguments: a boolean condition, and the value to include if the condition is true.
For example, imagine you only want to show a user's email if the authenticated user making the request is an administrator.
// app/Http/Resources/UserResource.php
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
// Only include the email if the authenticated user is an admin
'email' => $this->when($request->user()->isAdmin(), $this->email),
];
}
If $request->user()->isAdmin() returns false, the email key will be completely omitted from the final JSON response.
The following video provides a great hands-on demonstration of this, along with other useful conditional helpers.
Laravel Advanced - Eloquent Api Resource - Complete Explanation
The video from Laratips walks through several examples of conditional attributes. Pay close attention to how when() and whenNotNull() are used.
Watch from 18:29 to 23:11. The presenter demonstrates: Using when() to show an email only for a user with the 'admin' type. The importance of wrapping the value in a closure for lazy evaluation. Using whenNotNull() as a concise way to exclude attributes that are null.
As the video showed, there are a few key helpers for attributes:
$this->when(condition, value): Includes thevalueif theconditionis true.$this->whenNotNull(value): A convenient shortcut to include avalueonly if it's notnull. This is great for cleaning up your API output by removing empty fields.$this->whenHas('attribute_name'): Includes the model's attribute only if it was loaded from the database. This is useful when you're usingselect()to fetch a partial set of columns.
Merging Multiple Attributes with mergeWhen()
Sometimes, you have a group of attributes that should all be included based on the same condition. Instead of writing multiple when() statements, you can use mergeWhen(). This method takes a condition and an array of attributes to merge into the resource's top level if the condition is true.
// app/Http/Resources/UserResource.php
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
// Merge these attributes if the user is an admin
$this->mergeWhen($request->user()->isAdmin(), [
'email' => $this->email,
'last_login_at' => $this->last_login_at,
'two_factor_enabled' => $this->two_factor_enabled,
]),
];
}
This keeps your resource definition much cleaner. Let's see it in action.
Laravel Advanced - Eloquent Api Resource - Complete Explanation
The same Laratips video has an excellent segment on mergeWhen(). It builds directly on the previous example.
Watch from 23:11 to 25:49. The video shows how to refactor two separate when() calls into a single, more readable mergeWhen() block.
For more examples and the official definitions, the Laravel documentation is your best friend.
Eloquent: API Resources - Conditional Attributes
The official documentation provides concise code examples for all the conditional attribute methods we've discussed.
Read the section 'Conditional Attributes', including the sub-section on 'Merging Conditional Attributes'. This will reinforce what you saw in the video.
2. Conditional Relationships & The N+1 Problem
Conditionally including relationships is even more critical than attributes, primarily for performance. Unconditionally loading relationships in a resource can easily lead to the infamous "N+1 query problem," which directly impacts your goal of mastering database optimization.
What is the N+1 problem?
Imagine you have an endpoint that returns a list of 10 blog posts. Your resource for each post is configured to show its comments ('comments' => $this->comments).
- You run 1 query to get the 10 posts.
- Then, as Laravel transforms each post through the resource, it sees
$this->commentsand executes a new query for each post to fetch its comments. That's 10 more queries.
Total: 1 (for posts) + 10 (one for each post's comments) = 11 queries. For N posts, it's N+1 queries. This can cripple your application's performance.
The solution is to eager load the relationship in your controller with with('comments'). But how does the resource know whether you've eager-loaded it or not?
The whenLoaded() Method
The whenLoaded('relationship_name') method is the answer. It checks if a relationship has already been loaded on the model. If it has, it's included in the response. If not, it's omitted, preventing the N+1 problem.
// app/Http/Resources/PostResource.php
use App\Http\Resources\CommentResource;
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
// This will only include comments if they were eager-loaded
'comments' => CommentResource::collection($this->whenLoaded('comments')),
];
}
Now, your controller dictates what gets included:
Post::all()-> Thecommentskey will be absent.Post::with('comments')->get()-> Thecommentskey will be present.
This video from Laravel Daily provides a fantastic, focused explanation of this exact scenario.
Laravel API Resources: whenLoaded() To Avoid N+1 Queries
This video clearly illustrates the N+1 problem and how whenLoaded() is the perfect tool within an API Resource to solve it.
First, watch from 01:27 to 02:50 to see the N+1 problem demonstrated with a query debugger. Then, watch from 03:05 to 03:58 to see how implementing whenLoaded() fixes the issue by making the resource 'smarter'.
By using whenLoaded(), you decouple your resource from your controller. The resource becomes a flexible template that adapts to the data it's given, which is a powerful pattern for reusable code.
Other Conditional Relationship Helpers
Just like with attributes, there are other helpers for relationships that follow the same principle:
$this->whenCounted('relationship_name'): Includes the relationship's count (e.g.,posts_count) only if it was loaded viawithCount().$this->whenPivotLoaded('pivot_table_name', closure): For many-to-many relationships, this includes data from the intermediate (pivot) table only when it's available.
Let's watch one more video that covers these additional helpers.
Laravel Advanced - Eloquent Api Resource - Complete Explanation
We'll return to the Laratips video, which demonstrates whenLoaded, whenCounted, and whenPivotLoaded in a practical context.
Watch these three short clips: whenLoaded (11:16 - 15:06): A different example of using whenLoaded with a nested RoleResource. whenCounted (25:49 - 28:01): See how to conditionally include a roles_count. whenPivotLoaded (28:01 - 30:07): Learn how to add data from a pivot table, like an expires_at timestamp on a role_user table.
Conclusion
You've now learned how to make your API resources dynamic and efficient. This is a fundamental skill for building professional, high-performance Laravel APIs.
Key Takeaways:
- Conditional Attributes: Use
when(),whenNotNull(), andmergeWhen()to control which fields appear in your response based on specific conditions, like user permissions or data availability. - Conditional Relationships: Always use
whenLoaded()for relationships to prevent the N+1 query problem. This makes your resources robust and performant, adapting to whether the controller has eager-loaded the data. - Specialized Helpers: Leverage
whenCounted()for relationship counts andwhenPivotLoaded()for pivot data in many-to-many relationships. - Decoupling: This entire approach decouples your resource's output from the controller's query, leading to more flexible and reusable code.
Up Next:
Now that you have precise control over the content of your API responses, our next lesson will focus on how clients access them. We will tackle API versioning, specifically implementing a URI-based strategy using route groups. This is essential for managing changes to your API over time without breaking existing client applications.