Introduction
A one-to-one relationship links exactly one parent record to one child. Classic examples: User and Profile, Order and Payment, Company and Settings.
Key Concepts
hasOne: Defined on the parent; points to a child that holds the foreign key.belongsTo: Defined on the child; points back to the parent via the foreign key.- Foreign key placement: The FK always lives on the
belongsToside — the child's table holdsparent_id. withDefault: A helper that returns an empty-but-valid related model instead ofnullwhen the relationship is absent.
Real World Context
Most applications have at least one 1:1 pair — user/profile, order/payment, company/settings. Knowing which side gets hasOne vs belongsTo (it's about foreign key location, not conceptual 'ownership') is the first thing to internalize.
Deep Dive
A one-to-one relationship links one record to exactly one other record. For example, a User might have one Profile.
Defining One-to-One
hasOne (Parent Side)
php<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasOne; class User extends Model { /** * Get the user's profile. */ public function profile(): HasOne { return $this->hasOne(Profile::class); } }
belongsTo (Child Side)
php<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class Profile extends Model { /** * Get the user that owns the profile. */ public function user(): BelongsTo { return $this->belongsTo(User::class); } }
Database Structure
users profiles
┌────┬──────────┐ ┌────┬─────────┬─────────────┐
│ id │ name │ │ id │ user_id │ bio │
├────┼──────────┤ ├────┼─────────┼─────────────┤
│ 1 │ "John" │◄────────────│ 1 │ 1 │ "Developer" │
│ 2 │ "Jane" │◄────────────│ 2 │ 2 │ "Designer" │
└────┴──────────┘ └────┴─────────┴─────────────┘
Migration for Profile
phpSchema::create('profiles', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->text('bio')->nullable(); $table->string('website')->nullable(); $table->string('location')->nullable(); $table->timestamps(); });
Using the Relationship
php// Access profile from user $user = User::find(1); $profile = $user->profile; // Returns Profile model or null echo $user->profile->bio; // "Developer" // Access user from profile $profile = Profile::find(1); $user = $profile->user; // Returns User model echo $profile->user->name; // "John"
Creating Related Records
php// Method 1: Create and associate $user = User::find(1); $profile = new Profile(['bio' => 'Developer']); $user->profile()->save($profile); // Method 2: Create directly $user->profile()->create(['bio' => 'Developer']); // Method 3: From the child side $profile = new Profile(['bio' => 'Developer']); $profile->user()->associate($user); $profile->save();
Customizing Keys
php// Custom foreign key public function profile(): HasOne { return $this->hasOne(Profile::class, 'author_id'); } // Custom local key public function profile(): HasOne { return $this->hasOne(Profile::class, 'user_id', 'user_uuid'); } // On the belongsTo side public function user(): BelongsTo { return $this->belongsTo(User::class, 'author_id'); }
One-to-One with Default
Return a default model if none exists:
phppublic function profile(): HasOne { return $this->hasOne(Profile::class)->withDefault(); } // With default attributes public function profile(): HasOne { return $this->hasOne(Profile::class)->withDefault([ 'bio' => 'No bio provided', ]); } // Usage - never returns null $user->profile->bio; // "No bio provided" if no profile exists
hasOne vs belongsTo
User (parent) Profile (child)
┌─────────────┐ ┌─────────────────┐
│ │ hasOne │ │
│ User │────────►│ Profile │
│ │ │ │
│ │◄────────│ user_id FK │
│ │belongsTo│ │
└─────────────┘ └─────────────────┘
- hasOne: "I have one of these" (User has one Profile)
- belongsTo: "I belong to this" (Profile belongs to User)
- Foreign key is on the belongsTo side (profiles.user_id)
Practical Example
php// In a controller public function show(User $user) { // Lazy load (makes 2 queries) return view('users.show', [ 'user' => $user, 'profile' => $user->profile, ]); // Better: Eager load (makes 1 query) $user = User::with('profile')->findOrFail($user->id); return view('users.show', compact('user')); } // In Blade <h1>{{ $user->name }}</h1> <p>{{ $user->profile?->bio ?? 'No bio' }}</p>
Common Pitfalls
- Putting the FK on the wrong side — If
users.profile_idinstead ofprofiles.user_id, Eloquent's inference breaks. - Accessing a null relationship without
?->orwithDefault()—$user->profile->biocrashes when the profile doesn't exist. - Lazy-loading relationships in a loop — Classic N+1.
Best Practices
- Place the FK on the
belongsToside — Profile belongs to User, soprofiles.user_idis the FK. - Use
?->orwithDefault()for optional sides — Prevents null crashes in templates. - Eager-load when iterating —
User::with('profile')->get()collapses N+1 to 2 queries.
Summary
hasOneon the parent,belongsToon the child, FK always on the child.- Access via
$user->profile(collection-of-one) or$profile->user(parent). - Create via
$user->profile()->create([...])or$profile->user()->associate($user). - Custom keys pass as extra arguments to
hasOne/belongsTo. withDefault()gives you an empty-but-valid model when the relationship is absent.