Laravel API Resources: Fieldsets, Relations &amp; Versioning | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Laravel API Resources: Sparse Fieldsets, Conditional Relationships, and Versioning        On this page       1. [  Beyond Basic toArray: Production-Grade API Resources ](#beyond-basic-codetoarraycode-production-grade-api-resources)
2. [  Sparse Fieldsets ](#sparse-fieldsets)
3. [  Conditional Relationships Without N+1 ](#conditional-relationships-without-n1)
4. [  Versioning Without Class Duplication ](#versioning-without-class-duplication)
5. [  Takeaways ](#takeaways)

  ![Laravel API Resources: Sparse Fieldsets, Conditional Relationships, and Versioning](https://cdn.msaied.com/400/67fe9d3c6c505a6f67092e2761690696.png)

  #laravel   #api   #resources   #json-api  

 Laravel API Resources: Sparse Fieldsets, Conditional Relationships, and Versioning 
====================================================================================

     9 Jul 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Beyond Basic toArray: Production-Grade API Resources  ](#beyond-basic-codetoarraycode-production-grade-api-resources)
2. [  02   Sparse Fieldsets  ](#sparse-fieldsets)
3. [  03   Conditional Relationships Without N+1  ](#conditional-relationships-without-n1)
4. [  04   Versioning Without Class Duplication  ](#versioning-without-class-duplication)
5. [  05   Takeaways  ](#takeaways)

 Beyond Basic `toArray`: Production-Grade API Resources
------------------------------------------------------

Most Laravel tutorials stop at `$this->merge([...])`. In a real SaaS API you need three things most examples skip: sparse fieldsets so clients fetch only what they need, conditional relationship loading that doesn't trigger N+1 queries, and a versioning strategy that doesn't mean copy-pasting resource classes.

---

Sparse Fieldsets
----------------

The JSON:API spec defines `?fields[users]=id,name,email`. Implementing it cleanly inside a resource keeps the controller unaware of field filtering.

```php
// app/Http/Resources/Concerns/SparseFieldset.php
trait SparseFieldset
{
    protected function sparseFields(string $type, array $fields): array
    {
        $requested = request()
            ->input("fields.{$type}");

        if (! $requested) {
            return $fields;
        }

        $allowed = array_flip(explode(',', $requested));

        return array_filter(
            $fields,
            fn ($key) => isset($allowed[$key]),
            ARRAY_FILTER_USE_KEY
        );
    }
}

```

```php
// app/Http/Resources/UserResource.php
class UserResource extends JsonResource
{
    use SparseFieldset;

    public function toArray(Request $request): array
    {
        return $this->sparseFields('users', [
            'id'         => $this->id,
            'name'       => $this->name,
            'email'      => $this->email,
            'created_at' => $this->created_at->toIso8601String(),
        ]);
    }
}

```

A request to `GET /users?fields[users]=id,name` now returns only those two keys. The trait is reusable across every resource type.

---

Conditional Relationships Without N+1
-------------------------------------

Laravel's `whenLoaded` is well-known, but the pattern breaks down when you want to include a relationship only when the client explicitly requests it via `?include=posts`.

```php
// app/Http/Resources/UserResource.php
public function toArray(Request $request): array
{
    $includes = array_flip(
        explode(',', $request->input('include', ''))
    );

    return $this->sparseFields('users', [
        'id'    => $this->id,
        'name'  => $this->name,
        'posts' => $this->when(
            isset($includes['posts']) && $this->relationLoaded('posts'),
            fn () => PostResource::collection($this->posts)
        ),
    ]);
}

```

The controller is responsible for eager-loading based on the same `include` parameter:

```php
// app/Http/Controllers/UserController.php
public function index(Request $request): AnonymousResourceCollection
{
    $allowed = ['posts', 'team'];
    $includes = array_intersect(
        explode(',', $request->input('include', '')),
        $allowed
    );

    $users = User::with($includes)
        ->paginate(25);

    return UserResource::collection($users);
}

```

This pattern keeps the resource honest: it will never serialize a relationship that wasn't loaded, and the controller never leaks transformation logic.

---

Versioning Without Class Duplication
------------------------------------

The naive approach is `V1\UserResource` and `V2\UserResource`. That quickly becomes a maintenance nightmare. A cleaner approach uses a version-aware base resource and a small resolver.

```php
// app/Http/Resources/UserResource.php
class UserResource extends JsonResource
{
    use SparseFieldset;

    public function toArray(Request $request): array
    {
        $base = [
             'id'   => $this->id,
             'name' => $this->name,
        ];

        return match ($this->apiVersion($request)) {
            2       => array_merge($base, $this->v2Fields()),
            default => $base,
        };
    }

    private function v2Fields(): array
    {
        return [
            'email'      => $this->email,
            'created_at' => $this->created_at->toIso8601String(),
        ];
    }

    private function apiVersion(Request $request): int
    {
        // Accept: application/vnd.api+json; version=2
        preg_match('/version=(\d+)/', $request->header('Accept', ''), $m);
        return isset($m[1]) ? (int) $m[1] : 1;
    }
}

```

Version detection lives in one place. Adding V3 means adding a `v3Fields()` method and a new `match` arm — not a new file tree.

---

Takeaways
---------

- **Sparse fieldsets** belong in a reusable trait, not in controllers or query scopes.
- **`whenLoaded` + explicit include parsing** prevents accidental N+1 while keeping resources declarative.
- **Version branching inside a single resource class** is maintainable up to ~3 versions; beyond that, extract a versioned transformer layer.
- Always whitelist `include` values in the controller — never pass user input directly to `with()`.
- Pair resources with `JsonResource::withoutWrapping()` only at the outermost collection level to avoid breaking nested serialization.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-api-resources-sparse-fieldsets-conditional-relationships-and-versioning-1&text=Laravel+API+Resources%3A+Sparse+Fieldsets%2C+Conditional+Relationships%2C+and+Versioning) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-api-resources-sparse-fieldsets-conditional-relationships-and-versioning-1) 

 Frequently Asked Questions 
----------------------------

  2 questions  

     Q01  Does sparse fieldset filtering affect database queries?        Not automatically. The trait filters the PHP array after the model is hydrated. To push field selection to the database you would need to parse the fields parameter in the controller and pass a select() clause — useful for wide tables but adds coupling between HTTP and query layers. 

      Q02  When should I move to separate versioned resource classes instead of branching inside one class?        Once a version introduces structural changes — renamed keys, removed relationships, or different pagination envelopes — a shared class becomes hard to reason about. A practical rule: branch inside one class for additive changes (new fields), split into separate classes when you need to remove or rename existing keys. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Laravel New in 12: First-Class Typed Config, Slim Skeletons, and Upgrade Notes](https://cdn.msaied.com/401/bd0219f2cb584b037df76f985db348f8.png) laravel laravel-12 php 

### Laravel New in 12: First-Class Typed Config, Slim Skeletons, and Upgrade Notes

Laravel 12 ships with typed configuration objects, a leaner application skeleton, and several quality-of-life...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 9 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-new-in-12-first-class-typed-config-slim-skeletons-and-upgrade-notes) [ ![Laravel Eloquent Global Scopes: Bootable Traits, Scope Removal, and Testing Pitfalls](https://cdn.msaied.com/399/71a080bda5c9aa713a95cec2c8b42247.png) laravel eloquent testing 

### Laravel Eloquent Global Scopes: Bootable Traits, Scope Removal, and Testing Pitfalls

Global scopes silently shape every query on a model. Learn how to write bootable trait scopes, safely remove t...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 9 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-eloquent-global-scopes-bootable-traits-scope-removal-and-testing-pitfalls) [ ![API Rate-Limiting in Laravel: Sliding Windows, Per-Route Limiters, and Redis Precision](https://cdn.msaied.com/398/bb46e069aecd78f39c7ac31e2e1b13e2.png) laravel api redis 

### API Rate-Limiting in Laravel: Sliding Windows, Per-Route Limiters, and Redis Precision

Go beyond the default throttle middleware. Learn how to build sliding-window rate limiters, per-user dynamic l...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 9 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/api-rate-limiting-in-laravel-sliding-windows-per-route-limiters-and-redis-precision) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
