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.
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?
- Consistency: Create a standardized format for all API responses
- Transformation: Rename fields, format values, and calculate properties
- Selective Exposure: Only expose the data clients need to see
- Relationship Handling: Include or exclude related resources based on need
- Versioning: Maintain different resource transformations for API versions
- Conditional Fields: Dynamically include fields based on user permissions
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
- Consistency: Maintain a consistent structure across all your API resources
- Group Related Data: Use nested arrays to group related attributes (like we did with 'pricing', 'inventory', etc.)
- Performance: Use
whenLoadedto prevent N+1 query issues - Versioning: Consider namespacing resources for different API versions
- Documentation: Document the expected format of your resources
- Pagination: Always paginate collection responses for large datasets
- Caching: Consider caching resource responses for frequently accessed data
Practice Activities
Exercise 1: Basic Resource Transformation
Create a UserResource that transforms a User model, including:
- Full name (combining first and last name)
- A properly formatted date of birth
- The user's role name instead of role_id
- A gravatar URL based on the user's email
Exercise 2: Relationship Resource
Extend the UserResource to include:
- The user's posts using a PostResource
- The user's comments using a CommentResource
- Make sure both are only included when relations are loaded
Exercise 3: Conditional Fields
Modify your UserResource to:
- Only include the user's email if the authenticated user is an admin or the user themselves
- Include a 'verified' field only if the user is verified
- Include sensitive data like address only for admins
Exercise 4: Collection Transformation
Create a UserCollection resource that:
- Includes metadata about the total users, active users count, and admin count
- Adds pagination links
- Includes the current server time
Summary
Laravel API Resources provide a powerful, elegant way to transform your data for API responses. They allow you to:
- Transform database models into consistent JSON structures
- Include calculated fields and format existing ones
- Conditionally include data based on user roles or request parameters
- Efficiently handle relationships to prevent performance issues
- Add metadata and additional information to your responses
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.