Understanding Laravel Eloquent Relationships Through Real Examples
- #laravel
- #eloquent
- #database
- #tutorial
Almost every application works with related data. A user can have multiple projects, an order can contain several products, and an employee can participate in multiple training sessions.
In Laravel, Eloquent ORM makes these relationships easier to define, query, and maintain. Instead of manually writing SQL joins for every operation, you can describe how your models relate to each other and let Eloquent handle much of the underlying database interaction.
Understanding these relationships is essential for building applications with a clear data model and predictable business logic.
The examples below are illustrative and use a simple project management application.
1. One-to-One: hasOne() and belongsTo()
A one-to-one relationship connects one record to at most one related record.
For example, a user may have one profile containing additional information.
The User model defines the relationship:
use Illuminate\Database\Eloquent\Relations\HasOne;
public function profile(): HasOne
{
return $this->hasOne(Profile::class);
}The Profile model defines the inverse relationship:
use Illuminate\Database\Eloquent\Relations\BelongsTo;
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}The profiles table would typically contain a user_id foreign key. To enforce the one-to-one constraint at the database level, that foreign key should also be unique.
You can then retrieve the profile through the user:
$user = User::findOrFail($id);
$profile = $user->profile;Why this matters: the relationship expresses the domain model directly in your code. The unique database constraint ensures that the one-to-one rule is enforced even when records are created outside Eloquent.
2. One-to-Many: hasMany() and belongsTo()
One-to-many relationships are common in business applications.
For example, a developer can create multiple projects, while each project belongs to one developer.
In the User model:
use Illuminate\Database\Eloquent\Relations\HasMany;
public function projects(): HasMany
{
return $this->hasMany(Project::class);
}In the Project model:
use Illuminate\Database\Eloquent\Relations\BelongsTo;
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}Assuming projects contains a user_id foreign key, you can retrieve the projects belonging to a user:
$user = User::findOrFail($id);
foreach ($user->projects as $project) {
echo $project->name;
}You can also create a project through the relationship:
$user->projects()->create([
'name' => 'Portfolio Website',
]);Eloquent automatically associates the new project with the user. The Project model must permit the name attribute to be mass assigned, for example through an appropriate $fillable configuration.
Why this matters: creating related records through a relationship reduces the risk of forgetting the foreign key and makes the intended ownership explicit.
3. Many-to-Many: belongsToMany()
Some relationships cannot be represented by a single foreign key.
Consider developers participating in challenges. A developer can participate in several challenges, and each challenge can have multiple participants.
This requires an intermediate table, often called a pivot table.
In the User model:
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
public function challenges(): BelongsToMany
{
return $this->belongsToMany(Challenge::class);
}In the Challenge model:
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
public function participants(): BelongsToMany
{
return $this->belongsToMany(User::class);
}With Laravel's naming conventions, the intermediate table would be challenge_user, containing user_id and challenge_id.
You can associate an existing challenge with a user:
$user->challenges()->attach($challengeId);However, calling attach() repeatedly with the same IDs can create duplicate associations unless the database prevents them. Use a unique constraint on the pair of foreign keys and choose syncWithoutDetaching() when you want to add associations without removing existing ones.
$user->challenges()->syncWithoutDetaching([
$challengeId,
]);Why this matters: many-to-many relationships allow the data model to represent shared participation without duplicating users or challenges.
If the association itself has business information, such as a completion date, score, or participation status, the pivot table can store those additional attributes.
4. Choosing the Right Relationship
| Relationship | Example | Typical database structure |
|---|---|---|
| One-to-one | User โ Profile | Foreign key with a unique constraint |
| One-to-many | User โ Projects | Foreign key on the projects table |
| Many-to-many | Users โ Challenges | Intermediate table with two foreign keys |
The right choice depends on the actual business rules, not simply on which relationship is easiest to code.
For instance, if a training activity has multiple participants and each participant has a role and a culture priority, a many-to-many association with additional pivot attributes may be appropriate. If that association becomes a complex business entity with its own lifecycle, a dedicated model may be a better design.
5. Avoiding the N+1 Query Problem
Eloquent relationships make code readable, but accessing relationships inside loops can generate excessive database queries.
Consider this example:
$users = User::all();
foreach ($users as $user) {
echo $user->projects->count();
}Depending on the data and query pattern, this can execute one query to retrieve users and an additional query for each user's projects.
For relationship data that must be loaded, eager loading can help:
$users = User::with('projects')->get();If you only need the number of related records, use withCount() instead:
$users = User::withCount('projects')->get();
foreach ($users as $user) {
echo $user->projects_count;
}The key lesson is to choose the query pattern that matches the information you need. Eager loading retrieves related models, while withCount() calculates their count without loading every related model into memory.
Laravel Eloquent relationships provide a readable way to model how data connects across an application. Understanding hasOne(), hasMany(), belongsTo(), and belongsToMany() helps you build clearer models and express business rules more accurately.
The most important step is to design relationships around real domain constraints, reinforce those constraints in the database, and pay attention to query behavior as the application grows.
A well-designed relationship is more than a convenient ORM method: it is part of the application's data architecture.