Using API Resource Collections in Laravel

Laravel is one of the most powerful and expressive PHP frameworks, and one of its most valuable features for API development is the use of API Resources and Resource Collections. When building REST APIs, it is important to structure your data output consistently, securely, and efficiently. API Resource Collections help you accomplish this by transforming lists of data into well-structured JSON responses. Instead of returning raw Eloquent models or arrays, you can use Resources and Collections to ensure clean format, better control, pagination support, additional metadata, and maintainable output transformation logic.

This explains everything you need to know about API Resource Collections in Laravel. You will learn what they are, why they are important, how they differ from single resources, how to create them, how to format response lists, how to attach metadata, how to work with pagination, how to nest resources, how to modify collections globally, how to filter output fields, and how to follow industry standards like JSON:API style formatting.

By the end of this article, you will be able to build clean, scalable, and production-ready API responses using Laravel Resource Collections.

Understanding What API Resource Collections Are

In Laravel, an API Resource is a class that transforms a single model instance into a structured array format. A Resource Collection, however, is used for lists of data such as:

  • all products
  • all users
  • all posts
  • all orders
  • all categories

A Collection ensures your API returns consistent JSON across the entire application.

For example, instead of getting raw Eloquent output like:

[
{
    "id": 1,
    "name": "Laptop",
    "price": 1200,
    "created_at": "2024-01-15T10:00:00Z",
    "updated_at": "2024-01-15T10:00:00Z"
},
...
]

A Resource Collection allows you to define exactly:

  • which fields to show
  • how to format them
  • how to add metadata
  • how to shape pagination

The Resource Collection becomes the official structure of your API’s response.


Why API Resource Collections Are Important

Modern APIs require:

  • consistent response structures
  • secure data exposure
  • ability to hide sensitive attributes
  • flexibility in formatting
  • easy pagination
  • additional metadata like counts
  • nested relationships

Raw Eloquent responses can leak sensitive fields like passwords or internal status flags. Collections help prevent that.

Collections also improve maintainability. Instead of formatting output in controllers or repeatedly writing array transformations, all formatting logic lives inside the Resource class.


How Resource Collections Work in Laravel

Laravel comes with two main ways to create resource collections:

  1. Using the collection() method of a Resource
  2. Creating a dedicated Resource Collection class

Example 1: using collection method

return ProductResource::collection(Product::all());

Example 2: using a dedicated collection

return new ProductCollection(Product::paginate(10));

Both approaches format output consistently, but dedicated collection classes offer more control.


Creating a Resource for API Output

First, create a resource for single items.

php artisan make:resource ProductResource

This generates:

app/Http/Resources/ProductResource.php

Inside:

public function toArray($request)
{
return [
    'id' => $this->id,
    'title' => $this->name,
    'price' => number_format($this->price, 2),
    'created' => $this->created_at->toDateString()
];
}

Now every Product will be formatted like this.


Creating a Resource Collection

Create a dedicated collection class:

php artisan make:resource ProductCollection

It generates:

app/Http/Resources/ProductCollection.php

Default structure:

public function toArray($request)
{
return [
    'data' => $this->collection,
];
}

This is where you control:

  • output structure
  • metadata
  • pagination
  • links
  • wrapper names

Returning a Collection in a Controller

Example controller method:

public function index()
{
return new ProductCollection(Product::all());
}

Or with pagination:

return new ProductCollection(Product::paginate(10));

Laravel automatically handles pagination meta fields.


Using Resource Collections With API Routes

Routes:

Route::get('/products', [ProductController::class, 'index']);

Controller:

return ProductResource::collection(Product::all());

Or dedicated:

return new ProductCollection(Product::paginate());

Both approaches are valid.


How Resource Collections Improve Consistency

Without collections, you might accidentally return:

  • arrays
  • objects
  • mixed formats
  • inconsistent keys
  • uppercase/lowercase mismatches

With Resource Collections, every list of products always has the same structure.


Transforming the Data Structure of Collections

You can transform each item inside the collection:

public function toArray($request)
{
return [
    'products' => $this->collection->transform(function ($product) {
        return [
            'id' => $product->id,
            'title' => $product->name,
            'price' => $product->price,
        ];
    }),
];
}

This ensures all items follow the same format.


Adding Metadata to Resource Collections

You can add extra fields beyond data.

Example:

public function toArray($request)
{
return [
    'count' => $this->collection->count(),
    'items' => ProductResource::collection($this->collection),
];
}

Metadata examples:

  • total items
  • filters applied
  • user role
  • message
  • version number

Using the with Method to Add Additional Data

You can use the with() method:

public function with($request)
{
return [
    'status' => 'success',
    'api_version' => '1.0',
];
}

Final output:

{
  "data": [...],
  "status": "success",
  "api_version": "1.0"
}

Working With Pagination in Resource Collections

Resource Collections fully support pagination.

Controller:

return new ProductCollection(Product::paginate(10));

Response automatically includes:

  • total
  • per_page
  • current_page
  • last_page
  • links

You can customize them:

public function toArray($request)
{
return [
    'meta' => [
        'total_items' => $this->total(),
        'per_page' => $this->perPage(),
        'current_page' => $this->currentPage(),
    ],
    'data' => ProductResource::collection($this->collection),
];
}

Wrapping Collections in Custom Keys

Default resource collections wrap data in a data key.

But you can customize the key:

public static $wrap = 'products';

Now your output becomes:

{
  "products": [...]
}

Or remove wrapping:

public static $wrap = null;

Filtering Resource Output Fields

You may want different fields depending on:

  • admin vs normal user
  • mobile vs web
  • authenticated vs guest

Use conditional output:

public function toArray($request)
{
return [
    'id' => $this->id,
    'title' => $this->name,
    'price' => $this->price,
    'secret' => $this->when(auth()->user()?->isAdmin(), $this->secret),
];
}

Only admins get the secret field.


Conditional Relationships Inside Collections

Laravel allows including relationships only when needed.

'reviews' => ReviewResource::collection($this->whenLoaded('reviews')),

Load relationship only when:

Product::with('reviews')->get();

This keeps your API lean.


Nesting Resources Inside Collections

You can nest multiple resources:

return [
'id' => $this->id,
'title' => $this->name,
'brand' => new BrandResource($this->brand),
'categories' => CategoryResource::collection($this->categories),
];

Transforming Paginated Data With Resources

Paginated collections automatically apply Resources to every item.

return ProductResource::collection(Product::paginate(10));

Or use a custom collection:

return new ProductCollection(Product::paginate(10));

Complex Example: Adding Links to Collections

Hypermedia-style formatting:

public function toArray($request)
{
return [
    'data' => ProductResource::collection($this->collection),
    'links' => [
        'self' => url('/api/products'),
        'next' => $this->nextPageUrl(),
        'prev' => $this->previousPageUrl(),
    ],
];
}

Customizing the toResponse Method

For total control:

public function toResponse($request)
{
return response()->json([
    'status' => 'success',
    'payload' => $this->toArray($request),
]);
}

Using Anonymous Resource Collections

Laravel allows resource-only collections:

return ProductResource::collection($products);

This is useful for simple collections without metadata.


Using Resource Collections for Large Datasets

Collections improve:

  • structure
  • readability
  • memory usage (with pagination)
  • consistency

When handling large datasets, always use pagination.


Updating Resource Collections When API Changes

You can update the Resource without modifying controllers or routes.
This makes Resources ideal for versioning.

Example change:

Add a field:

'stock' => $this->stock

All controllers instantly return updated results.


Versioning Your API Using Collections

Versioning example:

app/Http/Resources/V1/ProductResource
app/Http/Resources/V2/ProductResource

Route version:

Route::prefix('v2')->get('/products', ...);

Each version can have different fields.


Using Resource Collections With Transformers

Resources can work side-by-side with:

  • Services
  • Repositories
  • DTOs
  • Transformers

This helps with domain-driven designs.


Converting Collection to JSON Style Structures

For JSON:API style:

'id' => (string) $this->id,
'type' => 'product',
'attributes' => [
'title' => $this->name,
'price' => $this->price,
]

Collections can enforce JSON:API formatting.


Returning Minimal Output or Extended Output

Use conditional checks:

return [
'id' => $this->id,
'title' => $this->name,
'details' => $this->when($request->query('details'), [
    'description' => $this->description,
    'stock' => $this->stock
])
];

Useful for mobile apps.


Adding Sorting and Filtering Inside Collections

You can modify collection data:

$this->collection->sortBy('price');

Or apply filtering:

$this->collection->filter(function ($item) {
return $item->price > 100;
});

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *