Reader Stacks

Eloquent Relationships: hasOne, hasMany, and belongsTo

These three relationship types cover the majority of real-world table structures — the trick is remembering which side of the foreign key each one describes.

Eloquent relationships describe how two tables connect through a foreign key — the three most common types, hasOne, hasMany, and belongsTo, cover most real schemas, and the way to tell them apart is by asking which table actually holds the foreign key column.

1. belongsTo — this model holds the foreign key

// posts table has a user_id column
class Post extends Model
{
    public function author(): BelongsTo
    {
        return $this->belongsTo(User::class, 'user_id');
    }
}
$post->author; // the User this post belongs to

belongsTo is used on the model whose own table contains the foreign key column — a posts.user_id column means Post belongsTo User, not the other way around. The second argument ('user_id') is only needed when the column doesn't follow Laravel's default naming convention ({relationship_name}_id).

2. hasMany — the other model holds the foreign key, and there can be several

class User extends Model
{
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }
}
$user->posts; // a Collection of every Post where posts.user_id = $user->id

hasMany is the inverse of belongsTo — defined on the model being referenced, describing that many rows in the other table can point back to one row here.

3. hasOne — the other model holds the foreign key, but there's only ever one

// profiles table has a user_id column, and each user has exactly one profile
class User extends Model
{
    public function profile(): HasOne
    {
        return $this->hasOne(Profile::class);
    }
}
$user->profile; // a single Profile model, or null

hasOne is structurally identical to hasMany — same foreign key direction — the only difference is that Eloquent returns a single model instance (or null) instead of a Collection, because the relationship is understood to be one-to-one rather than one-to-many.

4. The foreign key always lives on the "belongsTo" side

This is the one rule that resolves most confusion: whichever table's own column actually stores the foreign key ID is the "belongsTo" side of the relationship — the other model, referenced by that column, gets either hasOne or hasMany depending on whether one or many rows can point back to it.

5. Eager loading to avoid N+1 queries

// N+1 problem: 1 query for users, then 1 additional query per user for their posts
$users = User::all();
foreach ($users as $user) {
    echo $user->posts->count();
}

// Fixed: 2 queries total, regardless of how many users there are
$users = User::with('posts')->get();
foreach ($users as $user) {
    echo $user->posts->count();
}

with() eager-loads a relationship in a second, separate query up front — this is arguably the single most important Eloquent performance concept: accessing a relationship inside a loop without eager-loading it first triggers one additional query per iteration, which scales linearly (and badly) with the number of records.

6. Constraining a relationship query

class User extends Model
{
    public function publishedPosts(): HasMany
    {
        return $this->hasMany(Post::class)->where('status', 'published');
    }
}

Adding constraints directly inside a relationship definition creates a distinctly named, reusable relationship (here, publishedPosts alongside the unconstrained posts) — useful when a particular filtered view of a relationship is needed in several places throughout the app.

Topics: Database Queries & Eloquent