In real applications, database tables are related to each other. A user has many posts, a post belongs to a user, a post has many tags. Eloquent Relationships let you define these connections directly in your models, so you can access related data easily without writing complex SQL joins.
Relationship Types Overview
Relationship
Example
Foreign Key On
hasOne
User has one Profile
profiles table
belongsTo
Profile belongs to User
profiles table
hasMany
User has many Posts
posts table
belongsToMany
Post has many Tags
pivot table
hasOneThrough
Country has one Capital through City
intermediate table
1. One to One (hasOne / belongsTo)
A User has one Profile. The profiles table stores a user_id foreign key.
class User extends Model
{
// A user HAS MANY posts
public function posts()
{
return $this->hasMany(Post::class);
}
}
app/Models/Post.php
class Post extends Model
{
// A post BELONGS TO a user
public function user()
{
return $this->belongsTo(User::class);
}
}
Usage
// Get all posts by a user
$user = User::find(1);
foreach ($user->posts as $post) {
echo $post->title;
}
// Get the author of a post
$post = Post::find(1);
echo $post->user->name;
// Count user's posts
echo $user->posts()->count();
// Add a new post to user
$user->posts()->create([
'title' => 'New Post',
'body' => 'Content here...',
]);
3. Many to Many (belongsToMany)
A Post can have many Tags, and a Tag can belong to many Posts. This requires a pivot table (a third table that connects them).
Pivot table naming: Use both table names in singular form, in alphabetical order, separated by underscore. So post + tag = post_tag.
app/Models/Post.php
class Post extends Model
{
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}
app/Models/Tag.php
class Tag extends Model
{
public function posts()
{
return $this->belongsToMany(Post::class);
}
}
Usage
// Get all tags of a post
$post = Post::find(1);
foreach ($post->tags as $tag) {
echo $tag->name;
}
// Attach tags to a post (add to pivot table)
$post->tags()->attach([1, 2, 3]);
// Detach tags (remove from pivot table)
$post->tags()->detach([1]);
// Sync tags (replace all existing with these)
$post->tags()->sync([2, 3, 4]);
// Toggle tags (attach if missing, detach if exists)
$post->tags()->toggle([1, 2]);
4. Has Many Through
Access distant relations through an intermediate model. Example: A Country has many Posts through Users.
countries
id
name
1
Cambodia
2
Japan
users (intermediate)
id
country_id
name
1
1
John
2
1
Jane
3
2
Yuki
posts
id
user_id
title
1
1
Laravel
2
2
Vue.js
3
3
React
Country id: 1Users country_id: 1Posts user_id: 1, 2
Tables
countries: id, name
users: id, country_id, name
posts: id, user_id, title
app/Models/Country.php
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough(Post::class, User::class);
}
}
Usage
// Get all posts from a country (goes through users)
$country = Country::find(1);
foreach ($country->posts as $post) {
echo $post->title;
}
5. Eager Loading (N+1 Problem)
Without eager loading, accessing relations in a loop causes many extra database queries (the N+1 problem). Eager loading fixes this.
Bad: N+1 Problem (1 + N queries)
// This runs 1 query for posts + 1 query PER post for user
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name; // Extra query each time!
}
// If 100 posts = 101 queries!
Good: Eager Loading (2 queries total)
// with() loads the relation in advance
$posts = Post::with('user')->get();
foreach ($posts as $post) {
echo $post->user->name; // No extra query!
}
// Always just 2 queries regardless of post count!
// Load multiple relations
$posts = Post::with(['user', 'tags'])->get();
// Nested eager loading
$posts = Post::with('user.profile')->get();
// Eager load with conditions
$posts = Post::with(['comments' => function ($query) {
$query->where('approved', true)->latest();
}])->get();
Rule of thumb: Always use with() when you know you'll access a relation inside a loop. This is one of the most important performance optimizations in Laravel.
6. Querying Relationships
Useful Relationship Queries
// Get posts that HAVE at least one comment
$posts = Post::has('comments')->get();
// Get posts with 5 or more comments
$posts = Post::has('comments', '>=', 5)->get();
// Get posts that have comments containing "great"
$posts = Post::whereHas('comments', function ($query) {
$query->where('body', 'like', '%great%');
})->get();
// Get posts WITHOUT any comments
$posts = Post::doesntHave('comments')->get();
// Count relations
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
// Load relation after initial query
$post = Post::find(1);
$post->load('comments'); // Lazy eager loading
Summary
hasOne / belongsTo — one-to-one (User → Profile)
hasMany / belongsTo — one-to-many (User → Posts)
belongsToMany — many-to-many with pivot table (Posts ↔ Tags)
hasManyThrough — distant relations through intermediate model
Eager Loading — always use with() to avoid N+1 queries
has / whereHas — query based on relationship existence