Laravel Eloquent Relationships

When working with databases, one of the most common tasks is connecting related data. For example:

  • A user can have many posts.

  • A post can have many comments.

  • A student can enroll in many courses, and each course can have many students.

This type of data association is called a relationship.

In Laravel, managing these relationships is extremely easy thanks to Eloquent ORM (Object Relational Mapper). Instead of writing complex SQL joins manually, you can simply define relationships in your model classes and work with them like normal PHP objects.


What are Laravel Eloquent Relationships?

Laravel Eloquent Relationships allow you to define how models in your application are connected to each other.

In simple words:
Eloquent lets you say:

  • “A User has many Posts”

  • “A Post belongs to a User”

and then you can directly fetch related records using intuitive methods without writing SQL.


Why Use Eloquent Relationships?

  1. Readable & Clean Code

    • No need to write raw SQL joins repeatedly.

    • Example: $user->posts instead of SELECT * FROM posts WHERE user_id = 1.

  2. Faster Development

    • You define relationships once in models and reuse them everywhere.

  3. Maintainability

    • If your database schema changes, you only update the relationship in the model.

  4. Powerful Query Features

    • Laravel provides eager loading, filtering, aggregates, and advanced query options with relationships.

  5. Real-World Mapping

    • Relationships are based on real-world logic (Users ↔ Posts, Students ↔ Courses).


Types of Relationships in Laravel

Laravel provides different relationship types to cover almost every real-world case:

Relationship Type Example Laravel Methods
One-to-One A User has one Profile hasOne, belongsTo
One-to-Many A User has many Posts hasMany, belongsTo
Many-to-Many A Student can join many Courses belongsToMany
Has Many Through A Country → Users → Posts hasManyThrough
Polymorphic One-to-One A Post or Video has one Image morphOne, morphTo
Polymorphic One-to-Many A Post or Video can have many Comments morphMany, morphTo
Polymorphic Many-to-Many A Post or Video can have many Tags morphToMany, morphedByMany

 

1- One-to-One Relationship in Laravel Eloquent

A One-to-One relationship means that a record in one table is associated with exactly one record in another table.

Real-world examples:

  • A User has one Profile.

  • A Company has one Address.

  • A Car has one Engine.

In Laravel, One-to-One relationships are defined using the hasOne and belongsTo methods.


When to Use One-to-One?

Use One-to-One when:

  • Each entity has an additional set of details stored in a separate table.

  • You want to keep your tables organized and normalized instead of having one huge table.

📌 Example:
Instead of putting user profile details (bio, address, phone) in the users table, we create a separate profiles table.


Database Structure for One-to-One

users table profiles table
id (Primary Key) id (Primary Key)
name bio
email phone
... user_id (Foreign Key → users.id)

Here:

  • profiles.user_id is a foreign key referencing users.id.

  • Each profile belongs to exactly one user.


Step 1: Create Migrations

// users migration (already exists in most projects)
Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamps();
});

// profiles migration
Schema::create('profiles', function (Blueprint $table) {
    $table->id();
    $table->text('bio')->nullable();
    $table->string('phone')->nullable();
    $table->foreignId('user_id')->constrained()->onDelete('cascade');
    $table->timestamps();
});

 Here:

  • user_id links the profile to the user.

  • onDelete('cascade') ensures the profile is deleted if the user is deleted.


Step 2: Define Models

User Model (User.php)

class User extends Model
{
    use HasFactory;

    public function profile()
    {
        return $this->hasOne(Profile::class);
    }
}

Profile Model (Profile.php)

class Profile extends Model
{
    use HasFactory;

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

Step 3: Querying One-to-One Relationship

1. Get a User’s Profile 

$user = User::find(1);
$profile = $user->profile;

2. Get Profile’s User 

$profile = Profile::find(1);
$user = $profile->user;

3. Create a Profile for a User 

$user = User::find(1);
$user->profile()->create(
 [
   'bio' => 'I am a Laravel Developer.',
   'phone' => '123-456-7890',
 ]
);

 

4. Update Profile
$user->profile()->update([
    'bio' => 'I now specialize in Laravel Eloquent Relationships!',
]);

5. Eager Loading (Optimize Queries)

 
$users = User::with('profile')->get();

foreach ($users as $user) {
    echo $user->name . ' - ' . $user->profile->phone;
}

Real-World Example: User & Profile

Imagine a social networking site:

  • users table stores login information.

  • profiles table stores personal details like bio, address, and social links.

This design:
✅ Keeps data organized.
✅ Makes queries faster.
✅ Avoids a bloated users table.


 Advantages of One-to-One Relationship in Laravel

  • Better organization: Separate concerns into different tables.

  • Easier updates: Modify profile details without touching the users table.

  • Scalability: You can add more profile fields later without changing the users table.

2- One-to-Many Relationship in Laravel Eloquent

A One-to-Many relationship means that one record in a table can have multiple related records in another table.

 Real-world examples:

  • A User has many Posts.

  • A Category has many Products.

  • An Order has many Order Items.

In Laravel, this is defined using the hasMany and belongsTo methods.


When to Use One-to-Many?

Use One-to-Many when:

  • You want to represent ownership (e.g., a user owns posts).

  • A single parent can have multiple children.

  • You want to keep related data in separate tables for normalization.


Database Structure for One-to-Many

users table posts table
id (PK) id (PK)
name title
email body
... user_id (FK → users.id)

Here:

  • users.id = primary key.

  • posts.user_id = foreign key referencing users.id.


Step 1: Create Migrations

 
// users migration (already exists)
Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamps();
});

// posts migration
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('body');
    $table->foreignId('user_id')->constrained()->onDelete('cascade');
    $table->timestamps();
});

👉 user_id links each post to its author (user).


Step 2: Define Models

User Model (User.php)

class User extends Model
{
    use HasFactory;

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

Post Model (Post.php)

class Post extends Model
{
    use HasFactory;

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

Step 3: Querying One-to-Many Relationship

i. Get All Posts of a User

$user = User::find(1);
$posts = $user->posts;

foreach ($posts as $post) {
    echo $post->title;
}

ii. Get the Author of a Post

$post = Post::find(1);
$author = $post->user;
echo $author->name;

iii. Create a New Post for a User 

$user = User::find(1);

$user->posts()->create([
    'title' => 'My First Laravel Post',
    'body' => 'This is an example of One-to-Many relationship in Laravel.',
]);

iv. Eager Loading (Avoid N+1 Problem) 

$users = User::with('posts')->get();

foreach ($users as $user) {
    echo $user->name . ' has ' . $user->posts->count() . ' posts.';
}

v. Filtering Related Models 

$users = User::with(['posts' => function ($query) {
    $query->where('status', 'published');
}])->get();

Real-World Example: Blog Application

In a Blog System:

  • Each User writes multiple Posts.

  • Each Post belongs to one User.

This makes it easy to:
✅ Get all posts written by a specific user.
✅ Find the author of any post.
✅ Count how many posts a user has written.


Advantages of One-to-Many in Laravel

  • Simplicity: Easy to define and query.

  • Performance: Works with eager loading and withCount().

  • Real-world mapping: Matches common scenarios like users/posts, categories/products, orders/items.


3- Many-to-Many Relationship

A Many-to-Many relationship means that records in one table can relate to multiple records in another table, and vice versa.

👉 Real-world examples:

  • A Student can enroll in many Courses, and a Course can have many Students.

  • A User can have many Roles, and a Role can belong to many Users.

  • A Product can belong to many Categories, and a Category can contain many Products.

In Laravel, this is defined using the belongsToMany method.


When to Use Many-to-Many?

Use Many-to-Many when:

  • You need a two-way relationship.

  • Both entities can be linked to multiple records.

  • You want to store extra information in the relationship (e.g., enrollment date, role type).


Database Structure for Many-to-Many

Many-to-Many relationships require a pivot table.
For example:

students table courses table course_student (pivot table)
id (PK) id (PK) id (PK)
name title student_id (FK → students.id)
email description course_id (FK → courses.id)
... ... created_at / updated_at

📌 The pivot table (course_student) holds the relationship between students and courses.


Step 1: Create Migrations

// students migration
Schema::create('students', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamps();
});

// courses migration
Schema::create('courses', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('description')->nullable();
    $table->timestamps();
});

// pivot table migration (course_student)
Schema::create('course_student', function (Blueprint $table) {
    $table->id();
    $table->foreignId('student_id')->constrained()->onDelete('cascade');
    $table->foreignId('course_id')->constrained()->onDelete('cascade');
    $table->timestamps();
});

The pivot table course_student connects students and courses.


Step 2: Define Models

Student Model (Student.php)

class Student extends Model
{
    use HasFactory;

    public function courses()
    {
        return $this->belongsToMany(Course::class);
    }
}

Course Model (Course.php)

class Course extends Model
{
    use HasFactory;

    public function students()
    {
        return $this->belongsToMany(Student::class);
    }
}

Step 3: Querying Many-to-Many Relationship

1. Get All Courses of a Student 

$student = Student::find(1);
$courses = $student->courses;

foreach ($courses as $course) {
    echo $course->title;
}

2. Get All Students in a Course 

$course = Course::find(1);
$students = $course->students;

foreach ($students as $student) {
    echo $student->name;
}
 

Real-World Example: User ↔ Roles

In an authentication system:

  • A User can have many Roles (Admin, Editor, Customer).

  • A Role can belong to many Users.

Pivot table: role_user

This is used in RBAC (Role-Based Access Control) systems.


🔍 Advantages of Many-to-Many in Laravel

  • Handles complex relationships easily.

  • Pivot tables can store extra data.

  • Works well with eager loading and sync.

  • Matches real-world scenarios like courses, roles, categories, and tags.


4- Has Many Through Relationship

What is Has Many Through?

The Has Many Through relationship allows you to access records of a model through an intermediate model.

👉 Example:

  • A Country has many Users.

  • A User has many Posts.

  • So, a Country can access all of its Posts through its Users.

This avoids writing nested loops and makes queries much cleaner.


Database Structure Example: Country → Users → Posts

countries table users table posts table
id (PK) id (PK) id (PK)
name name title
code email content
... country_id (FK → countries.id) user_id (FK → users.id)

Step 1: Create Migrations 

// countries table
Schema::create('countries', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('code')->unique();
    $table->timestamps();
});

// users table
Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->foreignId('country_id')->constrained()->onDelete('cascade');
    $table->timestamps();
});

// posts table
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content')->nullable();
    $table->foreignId('user_id')->constrained()->onDelete('cascade');
    $table->timestamps();
});

Step 2: Define Models

Country Model (Country.php

class Country extends Model
{
    use HasFactory;

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

User Model (User.php)

class User extends Model
{
    use HasFactory;

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

    public function country()
    {
        return $this->belongsTo(Country::class);
    }
}

Post Model (Post.php)

class Post extends Model
{
    use HasFactory;

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

Step 3: Querying Has Many Through

Get All Posts of a Country 

$country = Country::find(1);
$posts = $country->posts;

foreach ($posts as $post) {
    echo $post->title;
}

Benefits of Has Many Through

  • Simplifies access to distant relationships.

  • Reduces nested queries.

  • Keeps code clean and maintainable.

  • Works with eager loading.


Real-World Use Case: Hospital System

  • Hospital → has many Doctors.

  • Doctor → has many Appointments.

  • Hospital → directly gets all Appointments via hasManyThrough.

class Hospital extends Model
{
    public function appointments()
    {
        return $this->hasManyThrough(Appointment::class, Doctor::class);
    }
}

Usage: 

$hospital = Hospital::find(1);
foreach ($hospital->appointments as $appointment) {
    echo $appointment->date;
}

5- Polymorphic Relationships

What is a Polymorphic Relationship?

A Polymorphic Relationship allows a model to belong to more than one type of model using a single association.

 Example:

  • A Comment can belong to a Post or a Video.

  • A Tag can belong to a Post or a Product.

  • A Like can belong to a Photo or a Video.

Instead of creating separate tables for each relation, we use one table with a morph type and morph ID.


Real-World Examples of Polymorphic Relationships

  1. Comments System

    • A comment can be added on posts, videos, or products.

  2. Tagging System

    • Tags can be applied to posts, videos, or photos.

  3. Likes or Reactions

    • A like can belong to a post, comment, or video.


Database Structure Example: Comments

We want comments to apply to both posts and videos.

posts table videos table comments table
id (PK) id (PK) id (PK)
title title body
content url commentable_id
... ... commentable_type

👉 Here:

  • commentable_id → The ID of the parent model (post_id or video_id).

  • commentable_type → The parent model type (App\Models\Post or App\Models\Video).


Step 1: Create Migrations

// posts table
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content')->nullable();
    $table->timestamps();
});

// videos table
Schema::create('videos', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('url');
    $table->timestamps();
});

// comments table (polymorphic)
Schema::create('comments', function (Blueprint $table) {
    $table->id();
    $table->text('body');
    $table->unsignedBigInteger('commentable_id');
    $table->string('commentable_type');
    $table->timestamps();
});

Step 2: Define Models

Comment Model (Comment.php) 

class Comment extends Model
{
    use HasFactory;

    public function commentable()
    {
        return $this->morphTo();
    }
}

Post Model (Post.php) 

class Post extends Model
{
    use HasFactory;

    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

Video Model (Video.php) 

class Video extends Model
{
    use HasFactory;

    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

Step 3: Querying Polymorphic Relationship

1. Add Comments to a Post 

$post = Post::find(1);
$post->comments()->create([
    'body' => 'This is a comment on a post.'
]);

2. Add Comments to a Video 

$video = Video::find(1);
$video->comments()->create([
    'body' => 'This is a comment on a video.'
]);

3. Get Comments of a Post 

$post = Post::find(1);
foreach ($post->comments as $comment) {
    echo $comment->body;
}

Real-World Example: Tags (Polymorphic Many-to-Many)

Another advanced case is polymorphic many-to-many.
Example: A Tag can belong to posts, videos, or products.

We use a pivot table taggables:

tags table taggables table
id (PK) tag_id
name taggable_id
... taggable_type

👉 Laravel provides morphToMany and morphedByMany for this.


6- Polymorphic Many-to-Many Relationship

What is Polymorphic Many-to-Many?

A Polymorphic Many-to-Many relationship allows a model to be related to multiple different models using a pivot table.

👉 Example:

  • A Tag can belong to both Posts and Videos.

  • A Category can be assigned to both Products and Services.

  • A Like can be applied to Posts, Photos, or Videos.

Instead of creating multiple pivot tables, Laravel uses one universal pivot table with morphToMany() and morphedByMany().


Real-World Examples of Polymorphic Many-to-Many

  1. Tagging System

    • A tag like "Technology" can belong to posts, videos, and podcasts.

  2. Categories

    • Categories like "Fashion" can belong to products, blogs, or articles.

  3. Likes / Reactions

    • A like can apply to posts, photos, and videos.


Database Structure Example: Tags for Posts & Videos

tags table posts table videos table taggables table
id (PK) id (PK) id (PK) tag_id (FK → tags.id)
name title title taggable_id
... content url taggable_type

👉 Here:

  • taggable_id → The ID of the related model (post_id or video_id).

  • taggable_type → The related model type (App\Models\Post, App\Models\Video).


Step 1: Create Migrations

// tags table
Schema::create('tags', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->timestamps();
});

// posts table
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content')->nullable();
    $table->timestamps();
});

// videos table
Schema::create('videos', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('url');
    $table->timestamps();
});

// taggables pivot table
Schema::create('taggables', function (Blueprint $table) {
    $table->id();
    $table->foreignId('tag_id')->constrained()->onDelete('cascade');
    $table->unsignedBigInteger('taggable_id');
    $table->string('taggable_type');
    $table->timestamps();
});

Step 2: Define Models

Tag Model (Tag.php)

 
class Tag extends Model
{
    use HasFactory;

    public function posts()
    {
        return $this->morphedByMany(Post::class, 'taggable');
    }

    public function videos()
    {
        return $this->morphedByMany(Video::class, 'taggable');
    }
}

Post Model (Post.php)

 
class Post extends Model
{
    use HasFactory;

    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}

Video Model (Video.php)

 
class Video extends Model
{
    use HasFactory;

    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}
 

Step 3: Querying Polymorphic Many-to-Many

1. Attach Tags to a Post

$post = Post::find(1);
$post->tags()->attach([1, 2]); // Add tags with IDs 1 and 2

2. Attach Tags to a Video

$video = Video::find(1);
$video->tags()->attach(3); // Add tag with ID 3

3. Get Tags of a Post

$post = Post::find(1);
foreach ($post->tags as $tag) {
    echo $tag->name;
}

4. Get Tags of a Video

$video = Video::find(1);
foreach ($video->tags as $tag) {
    echo $tag->name;
}

5. Get All Posts of a Tag

$tag = Tag::find(1);
foreach ($tag->posts as $post) {
    echo $post->title;
}

6. Get All Videos of a Tag

$tag = Tag::find(1);
foreach ($tag->videos as $video) {
    echo $video->title;
}

Real-World Use Case: Blog Tagging System

  • A Tag like "Laravel" applies to both Blog Posts and Tutorial Videos.

  • Instead of creating post_tag and video_tag, we use taggables for all.

👉 This reduces database complexity and keeps relationships flexible.

Final Words

By now, you should have a solid understanding of Laravel Eloquent Relationships—from the simplest One-to-One relationship to advanced concepts like Polymorphic Many-to-Many.

With these skills, you can confidently design and implement real-world applications such as:

  • Blogs with categories, tags, and comments.

  • E-commerce systems with products, orders, and reviews.

  • Social platforms with likes, followers, and media.

  • Learning management systems (LMS) with students, courses, and instructors.