Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

Laravel 13 One to Many Relationship: hasMany and belongsTo Tutorial

๐Ÿ› ๏ธ Laravel Tutorials4 min read

Laravel 13 One to Many Relationship: hasMany and belongsTo Tutorial

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 9, 2026
Laravel 13 One to Many Relationship: hasMany and belongsTo Tutorial

A one-to-many relationship in Laravel 13 means one parent model owns many child models, for example one Post with many Comment rows. You define hasMany() on the parent, belongsTo() on the child, and store a foreign key such as post_id on the child table.

This tutorial builds that setup step by step, then shows how to create, query and eager load related records without N+1 queries.

What do I need first?

  • Laravel 13 (current stable when this was written) and a PHP version it supports.
  • A configured database connection.
  • Two models: Post (parent) and Comment (child).

How do I create the models and migrations?

Code
php artisan make:model Post -m
php artisan make:model Comment -m

The child table needs a foreign key. foreignId()->constrained() creates the column and the database constraint in one line.

PHP
// posts migration
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('body');
    $table->timestamps();
});

// comments migration
Schema::create('comments', function (Blueprint $table) {
    $table->id();
    $table->foreignId('post_id')->constrained()->cascadeOnDelete();
    $table->string('author');
    $table->text('body');
    $table->timestamps();
});
Code
php artisan migrate

cascadeOnDelete() removes comments when their post is deleted. Leave it out if you want deletes to fail while comments exist.

How do I define hasMany and belongsTo?

JavaScript
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Post extends Model
{
    protected $fillable = ['title', 'body'];

    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}
JavaScript
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Comment extends Model
{
    protected $fillable = ['author', 'body'];

    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}

Laravel guesses the foreign key from the parent name, so Post gives post_id. If your column has another name, pass it as the second argument: hasMany(Comment::class, 'article_id').

How do I create related records?

Call create() on the relationship. Laravel fills post_id for you.

JavaScript
$post = Post::create(['title' => 'Hello', 'body' => 'First post']);

$post->comments()->create([
    'author' => 'Sara',
    'body'   => 'Nice article.',
]);

$post->comments()->createMany([
    ['author' => 'Ali',  'body' => 'Thanks for sharing.'],
    ['author' => 'Noor', 'body' => 'Very clear.'],
]);

Note that post_id is not in $fillable. You do not need it there because the relationship sets it.

How do I read related records?

PHP
$post = Post::find(1);

foreach ($post->comments as $comment) {
    echo $comment->author;
}

$recent = $post->comments()->latest()->take(5)->get();

$comment = Comment::find(1);
echo $comment->post->title;

Use $post->comments (no parentheses) to get the loaded collection. Use $post->comments() to get a query builder you can keep filtering.

How do I avoid the N+1 query problem?

Looping over posts and reading $post->comments inside the loop runs one extra query per post. Eager load instead.

PHP
// 2 queries total, not 1 + N
$posts = Post::with('comments')->latest()->paginate(10);

// count without loading the comments
$posts = Post::withCount('comments')->get();
echo $posts[0]->comments_count;

In development you can make Laravel throw an exception on accidental lazy loading by calling Model::preventLazyLoading(! app()->isProduction()); in AppServiceProvider::boot().

How do I filter parents by their children?

JavaScript
// posts that have at least one comment
$posts = Post::has('comments')->get();

// posts with 3 or more comments
$posts = Post::has('comments', '>=', 3)->get();

// posts with a comment by Sara
$posts = Post::whereHas('comments', fn ($q) => $q->where('author', 'Sara'))->get();

What are the common mistakes?

  • Missing foreign key column. The error "Unknown column post_id" means the migration or key name is wrong.
  • Forgetting eager loading. Pages get slower as rows grow. Check the query count with Laravel Debugbar or Telescope in development.
  • Using the property when you need the builder. $post->comments->where(...) filters in memory. $post->comments()->where(...) filters in SQL.
  • Mass assignment errors. Add child fields to $fillable or use $guarded deliberately.

How do I verify it works?

Open php artisan tinker and run the examples below, or write a feature test.

JavaScript
public function test_post_has_many_comments(): void
{
    $post = Post::create(['title' => 'T', 'body' => 'B']);
    $post->comments()->create(['author' => 'A', 'body' => 'C']);

    $this->assertCount(1, $post->fresh()->comments);
    $this->assertSame($post->id, $post->comments->first()->post->id);
}

FAQ

What is the difference between hasMany and belongsTo?

hasMany sits on the parent that owns many rows. belongsTo sits on the child that stores the foreign key.

Can I order the related records by default?

Yes. Chain it in the relationship: $this->hasMany(Comment::class)->latest().

Should I add an index to the foreign key?

foreignId()->constrained() creates a foreign key constraint, which most databases index. Check your database if you create the column another way.

References

Article Index & Topic Tags
LaravelDatabaseEloquentRelationships

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.