In this lesson, we will discuss relationships between tables and between Eloquent Models and how to return them from the API Resources.
First, we must create a new Model to have a relation with a category.
php artisan make:model Product -mf
database/migration/xxx_create_products_table.php:
public function up(): void{ Schema::create('products', function (Blueprint $table) { $table->id(); $table->foreignId('category_id')->constrained(); $table->string('name'); $table->text('description'); $table->integer('price'); $table->timestamps(); });}
app/Models/Product.php:
use Illuminate\Database\Eloquent\Relations\BelongsTo; class Product extends Model{ use HasFactory; protected $fillable = [ 'name', 'description', 'price', ]; public function category(): BelongsTo { return $this->belongsTo(Category::class); }}
database/factories/ProductFactory.php:
use App\Models\Category; class ProductFactory extends Factory{ public function definition(): array { return [ 'name' => fake()->word(), 'category_id' => Category::inRandomOrder()->first()->id, 'description' => fake()->paragraph(), 'price' => rand(1000, 99999), ]; }}
database/seeders/DatabaseSeeder.php:
use App\Models\Product; class DatabaseSeeder extends Seeder{ public function run(): void { Category::factory(10)->create(); Product::factory(20)->create(); }}
And run the migrations with the seed.
php artisan migrate:fresh --seed
Next, the Controller, Resource, and Route.
php artisan make:controller Api/ProductControllerphp artisan make:resource ProductResource
routes/api.php:
Route::get('/user', function (Request $request) { return $request->user();})->middleware(Authenticate::using('sanctum')); Route::get('categories', [\App\Http\Controllers\Api\CategoryController::class, 'index']);Route::get('categories/{category}', [\App\Http\Controllers\Api\CategoryController::class, 'show']); Route::get('products', [\App\Http\Controllers\Api\ProductController::class, 'index']);
In the Controller, we can return the product with category.
app/Http/Controllers/Api/ProductController.php:
use App\Models\Product; class ProductController extends Controller{ public function index() { $products = Product::with('category')->get(); return $products; }}
After going to /api/products
endpoint, we can see all fields from product and category.
As you remember, we can use Resources to hide or modify fields. In this case, we will change the price field. First, let's use the ProductResource
in the Controller and add fields in the Resource.
app/Http/Controllers/Api/ProductController.php:
class ProductController extends Controller{ public function index() { $products = Product::with('category')->get(); return $products; return ProductResource::collection($products); }}
app/Http/Resources/ProductResource.php:
class ProductResource extends JsonResource{ public function toArray(Request $request): array { return [ 'id' => $this->id, 'category_id' => $this->category_id, 'name' => $this->name, 'description' => $this->description, 'price' => number_format($this->price / 100, 2), 'category' => $this->category, ]; }}
We can see a slightly different result after going to /api/products
again. First, we have everything in data
. Then, we have the price in dollars and cents. And also, we have a category that is not changed using the CategoryResource
.
So, how do we reuse the CategoryResource
inside in the ProductResource
? We can call other Resources inside a Resource.
app/Http/Resources/ProductResource.php:
class ProductResource extends JsonResource{ public function toArray(Request $request): array { return [ 'id' => $this->id, 'category_id' => $this->category_id, 'name' => $this->name, 'description' => $this->description, 'price' => number_format($this->price / 100, 2), 'category' => $this->category, 'category' => CategoryResource::make($this->whenLoaded('category')), ]; }}
We also use the conditional relationship whenLoaded
to show the category only when it is eager loaded. This way, it is easier to avoid N+1 query problems. Now we can see that category is being shown also using API Resource.