Eloquent's belongsTo, hasMany, and hasOne relationships each describe a different real-world data shape — getting the direction right depends entirely on which table physically holds the foreign key, which is the detail that trips up most beginners.
belongsTo: the "child" side of a relationship
// posts table has a user_id column
class Post extends Model
{
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}
$post->author->name;
belongsTo is defined on the model whose own table physically holds the foreign key column (posts.user_id here) — the method name (author) doesn't need to match the related model's name, but the foreign key column and, where needed, the related model class must be specified correctly.
hasMany: the "parent" side, with multiple children
class User extends Model
{
public function posts(): HasMany
{
return $this->hasMany(Post::class, 'user_id');
}
}
$user->posts; // a collection of all this user's posts
hasMany is the inverse of belongsTo — it's defined on the model that does NOT hold the foreign key, and it returns a collection since one user can have many posts.
hasOne: a one-to-one relationship
// profiles table has a user_id column
class User extends Model
{
public function profile(): HasOne
{
return $this->hasOne(Profile::class, 'user_id');
}
}
class Profile extends Model
{
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}
$user->profile->bio;
hasOne is structurally similar to hasMany — the foreign key still lives on the related table — but it returns a single model instance instead of a collection, since exactly one related row is expected.
The most common beginner mistake: picking the wrong direction
// WRONG — if posts.user_id is the foreign key, this direction is backwards
class User extends Model
{
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}
The deciding question is always "which table's column points to the other table's primary key?" — whichever model's table holds that foreign key column is the one that gets belongsTo(); the other side always gets hasMany() or hasOne(), never the reverse.
Eager loading to avoid N+1 queries
$posts = Post::with('author')->get(); // 2 queries total, not N+1
foreach ($posts as $post) {
echo $post->author->name;
}
Accessing $post->author inside a loop without eager loading first triggers one additional query per post (the N+1 problem) — with('author') loads every needed author in a single additional query up front instead.
Defining the inverse relationship
Every hasMany/hasOne relationship should typically have a corresponding belongsTo defined on the related model — while not strictly required if the inverse direction is never actually used in the app, defining both directions is the more complete, future-proof convention.