API Resources and Transformations in Laravel

Transforming your data elegantly for API responses

Introduction to API Resources

When building APIs in Laravel, one of the most important aspects is controlling how your data is presented to the client. Your database structure isn't always the ideal format for your API consumers. This is where Laravel API Resources shine.

API Resources act as a transformation layer between your Eloquent models and the JSON responses sent back to clients. They provide an elegant, fluent way to format and transform data for your API.

graph TD A[Database/Eloquent Model] --> B[API Resource] B --> C[JSON Response] style B fill:#f9f,stroke:#333,stroke-width:2px

Think of API Resources like professional gift wrappers at a department store. Your database gives you the raw product (the gift), but resources allow you to present it beautifully wrapped with exactly what the recipient needs to see.

Why Use API Resources?

Real-world scenario: Imagine you have an e-commerce platform with a complex Product model containing 30+ columns including internal data like profit margins and supplier codes. Your mobile app only needs the product name, price, description, and images. API Resources let you cleanly deliver just what's needed.

Creating and Using API Resources

Creating a Resource

Laravel makes it easy to generate a new resource using Artisan:

php artisan make:resource ProductResource

This generates a new resource class in app/Http/Resources:

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class ProductResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'price' => $this->price,
            'description' => $this->description,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}

Using the Resource in a Controller

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Product;
use App\Http\Resources\ProductResource;

class ProductController extends Controller
{
    public function show(Product $product)
    {
        return new ProductResource($product);
    }
    
    public function index()
    {
        return ProductResource::collection(Product::paginate(10));
    }
}

Data Transformation Techniques

Field Renaming

Sometimes your database field names don't match what your API consumers expect.

return [
    'id' => $this->id,
    'product_name' => $this->name, // Renamed from 'name'
    'product_price' => $this->price, // Renamed from 'price'
    'details' => $this->description, // Renamed from 'description'
];

Value Formatting

Transform values to be more useful for clients:

return [
    'id' => $this->id,
    'name' => $this->name,
    'price' => [
        'raw' => $this->price,
        'formatted' => '$' . number_format($this->price, 2),
    ],
    'is_on_sale' => $this->discount > 0,
    'created_at' => $this->created_at->format('Y-m-d'),
];

Calculated Properties

Include data that doesn't exist directly in your database:

return [
    'id' => $this->id,
    'name' => $this->name,
    'price' => $this->price,
    'sale_price' => $this->price - ($this->price * $this->discount / 100),
    'discount_percentage' => $this->discount,
    'in_stock' => $this->quantity > 0,
    'stock_status' => $this->quantity > 10 ? 'high' : ($this->quantity > 0 ? 'low' : 'out_of_stock'),
];

Resource Collections

When returning multiple resources, you have two options:

Simple Collection

// In your controller
return ProductResource::collection(Product::all());

Custom Collection Resource

For more control, create a dedicated collection resource:

php artisan make:resource ProductCollection
namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class ProductCollection extends ResourceCollection
{
    public function toArray($request)
    {
        return [
            'data' => $this->collection,
            'meta' => [
                'total_products' => $this->collection->count(),
                'store_name' => 'My Awesome Store',
                'api_version' => '1.0',
            ],
            'links' => [
                'self' => url('/api/products'),
            ],
        ];
    }
}

Then use it in your controller:

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

Handling Relationships

Laravel makes it easy to include relationships in your resources:

Including a Single Relationship

// In ProductResource
public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'price' => $this->price,
        // Include the category relationship
        'category' => new CategoryResource($this->whenLoaded('category')),
    ];
}

The whenLoaded method only includes the relationship if it was eager loaded, preventing N+1 query issues:

// In your controller
return new ProductResource(Product::with('category')->find($id));

Including Multiple Relationships

return [
    'id' => $this->id,
    'name' => $this->name,
    'category' => new CategoryResource($this->whenLoaded('category')),
    'reviews' => ReviewResource::collection($this->whenLoaded('reviews')),
    'tags' => TagResource::collection($this->whenLoaded('tags')),
];

Conditional Attributes

API Resources allow you to conditionally include attributes based on various conditions:

Based on User Role

return [
    'id' => $this->id,
    'name' => $this->name,
    'price' => $this->price,
    // Only include cost for admins
    'cost' => $this->when(auth()->user()->isAdmin(), $this->cost),
    // Only include supplier info for admins or suppliers
    'supplier' => $this->when(
        auth()->user()->isAdmin() || auth()->user()->isSupplier(),
        new SupplierResource($this->supplier)
    ),
];

Based on Request Parameters

return [
    'id' => $this->id,
    'name' => $this->name,
    // Only include full description if detailed=true query param is present
    'description' => $this->when(
        $request->query('detailed'), 
        $this->description,
        Str::limit($this->description, 100)
    ),
];

Additional Resource Response Data

Sometimes you need to add data that's not directly tied to your resource model:

// In ProductResource or ProductCollection
public function with($request)
{
    return [
        'meta' => [
            'api_version' => '1.0',
            'server_time' => now()->toIso8601String(),
        ],
        'links' => [
            'self' => url('/api/products'),
            'documentation' => url('/api/docs/products'),
        ],
    ];
}

The resulting JSON would include this data alongside your resource:

{
    "data": {
        "id": 1,
        "name": "Awesome Product",
        "price": 99.99
    },
    "meta": {
        "api_version": "1.0",
        "server_time": "2025-05-10T12:00:00+00:00"
    },
    "links": {
        "self": "https://example.com/api/products",
        "documentation": "https://example.com/api/docs/products"
    }
}

Real-World Example: E-commerce API

Let's explore a comprehensive e-commerce API resource setup:

Product Resource

class ProductResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'slug' => $this->slug,
            'name' => $this->name,
            'brand' => $this->brand,
            'pricing' => [
                'regular_price' => $this->price,
                'formatted_price' => '$' . number_format($this->price, 2),
                'sale_price' => $this->when($this->isOnSale(), $this->getSalePrice()),
                'discount_percentage' => $this->when($this->isOnSale(), $this->discount),
                'on_sale' => $this->isOnSale(),
            ],
            'inventory' => [
                'in_stock' => $this->inStock(),
                'qty_available' => $this->when(
                    auth()->check() && auth()->user()->isVendor(), 
                    $this->quantity
                ),
                'stock_status' => $this->stockStatus,
            ],
            'details' => [
                'description' => $this->when(
                    $request->query('include_description', false), 
                    $this->description,
                    Str::limit($this->description, 150)
                ),
                'specifications' => $this->when(
                    $request->query('include_specs', false),
                    json_decode($this->specifications)
                ),
                'weight' => $this->weight,
                'dimensions' => [
                    'length' => $this->length,
                    'width' => $this->width,
                    'height' => $this->height,
                ],
            ],
            'media' => [
                'thumbnail' => $this->getThumbnailUrl(),
                'images' => ImageResource::collection($this->whenLoaded('images')),
            ],
            'category' => new CategoryResource($this->whenLoaded('category')),
            'tags' => TagResource::collection($this->whenLoaded('tags')),
            'reviews' => [
                'average_rating' => $this->reviews_avg_rating ?? 0,
                'count' => $this->reviews_count ?? 0,
                'items' => ReviewResource::collection($this->whenLoaded('reviews')),
            ],
            'urls' => [
                'view' => route('products.show', $this->slug),
                'add_to_cart' => route('api.cart.add', $this->id),
            ],
            'created_at' => $this->created_at->toIso8601String(),
            'updated_at' => $this->updated_at->toIso8601String(),
        ];
    }
    
    public function with($request)
    {
        return [
            'meta' => [
                'currency' => 'USD',
                'tax_rate' => config('shop.tax_rate'),
            ],
        ];
    }
}

This comprehensive resource transforms a complex product model into a structured, clear API response with grouped related data.

Best Practices

flowchart LR A[Database] --> B[Eloquent Model] B --> C[Query + Load Relations] C --> D[API Resource] D --> E[JSON Response] D -.-> F[Cache] F -.-> E

Practice Activities

Exercise 1: Basic Resource Transformation

Create a UserResource that transforms a User model, including:

Exercise 2: Relationship Resource

Extend the UserResource to include:

Exercise 3: Conditional Fields

Modify your UserResource to:

Exercise 4: Collection Transformation

Create a UserCollection resource that:

Summary

Laravel API Resources provide a powerful, elegant way to transform your data for API responses. They allow you to:

By mastering API Resources, you can create clean, consistent, and performant APIs that expose exactly the data your clients need in exactly the format they expect.

In our next lecture, we'll explore authentication for Laravel APIs using Laravel Passport.