Module 5: Advanced Eloquent

Eloquent Relationships and Eager Loading.

Connecting Your Data

Welcome to Module 5! So far, our `users` and `posts` tables have existed in isolation. But in a real blog, posts belong to users. This connection is called a relationship. Properly defining relationships is the key to building complex, real-world applications and is arguably the most powerful feature of Eloquent.

In this module, you'll learn how to define these relationships, how to use them to easily fetch related data, and how to solve a major performance pitfall known as the "N+1 query problem" using eager loading.


1The One-To-Many Relationship: Users & Posts

The most common type of relationship is "one-to-many". In our case:

  • One User can have many Posts.
  • One Post belongs to one User.

1. The Database Schema

To create this link, the `posts` table needs a column to store the ID of the user who created it. This is called a foreign key. Let's create a new migration to add a `user_id` column to our `posts` table.

php artisan make:migration add_user_id_to_posts_table --table=posts

Open the new migration file and modify the `up()` method:

public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->foreignId('user_id')->constrained()->onDelete('cascade');
    });
}

This single line is incredibly powerful. `foreignId('user_id')` creates the column. `constrained()` automatically links it to the `id` on the `users` table. `onDelete('cascade')` means if a user is deleted, all of their posts will be deleted too. Run the migration:

php artisan migrate

2. The Eloquent Models

Now we tell our Eloquent models about this relationship. In the `User` model (`app/Models/User.php`), we define that a user `hasMany` posts:

public function posts()
{
    return $this->hasMany(Post::class);
}

In the `Post` model (`app/Models/Post.php`), we define the inverse: a post `belongsTo` a user:

public function user()
{
    return $this->belongsTo(User::class);
}

2The N+1 Query Problem (And How to Fix It)

Now that we have relationships, we can easily get the author for each post. Imagine in our `posts/index.blade.php` file, we want to show the author's name:

@foreach ($posts as $post)
    <h2>{{ $post->title }}</h2>
    <p>By: {{ $post->user->name }}</p> // Accessing the relationship
@endforeach

This code works, but it hides a massive performance problem. If you have 20 posts on the page, this will execute 21 database queries!

  • 1 Query to get all the posts. (`SELECT * FROM posts`)
  • 20 more Queries, one for each post, to get its author. (`SELECT * FROM users WHERE id = ?`)

This is the infamous N+1 query problem. It can slow your application to a crawl.

The Solution: Eager Loading

Eager loading is Laravel's solution. It lets you fetch all the related data in a single, additional query. You do this with the `with()` method in your controller.

Let's fix the `index` method in `PostController`:

// Before (The N+1 Problem)
$posts = Post::all(); // This will cause N+1 queries in the view

// After (The Fix!)
$posts = Post::with('user')->get(); // Eager load the 'user' relationship

With this one change, Laravel now runs only 2 queries, no matter how many posts you have:

  1. `SELECT * FROM posts`
  2. `SELECT * FROM users WHERE id IN (1, 2, 3, ...)`

This is dramatically more efficient. Always eager load relationships when looping over models.


3Practical Application: Assigning Authors

Let's update our application to use these new relationships.

1. Associate Posts with the Logged-in User

When a user creates a post, we need to save their ID with it. Let's update the `store` method in `PostController.php`.

public function store(Request $request)
{
    $validatedData = $request->validate([ ... ]);

    $request->user()->posts()->create($validatedData); // The magic!

    return redirect('/posts');
}

This one line is beautiful. `$request->user()` gets the currently authenticated user. `->posts()` accesses their "posts" relationship. `->create()` builds a new Post model instance and automatically sets the `user_id` for you before saving it to the database.

2. Display the Author's Name

Now, let's update the `index` method in `PostController` to eager load the author and the `posts/index.blade.php` view to display it.

PostController.php:

public function index()
{
    $posts = Post::with('user')->latest()->get(); // Eager load + order by newest
    return view('posts.index', ['posts' => $posts]);
}

posts/index.blade.php:

@extends('layouts.app')

@section('content')
    @foreach ($posts as $post)
        <h2>{{ $post->title }}</h2>
        <p>By: {{ $post->user->name }} on {{ $post->created_at->toFormattedDateString() }}</p>
        <p>{{ $post->content }}</p>
        <hr>
    @endforeach
@endsection