
Many developers, when adding a “reply to comment” feature in a Laravel app, often consider creating a separate sub_comments
table (I was one of them, based on my lack of experience, I know this later). It sounds intuitive, because a reply is different from a comment, right? logic thinking
But there’s a cleaner, more strict way: self-referencing relationships in the samecomments
table.
The Idea: one table to rule them all (Comments and Replies)
Instead of a separate sub_comments
table, we just add a parent_id
column to the existing comments
table. This column references another comment by id in the same table, the one being replied to.
This technique is called a self-referencing relationship, and Laravel supports it in a clean way.
1- Update the comments table migration
In your migration, modify the comments
table to include a parent_id
column:
Schema::table('comments', function (Blueprint $table) {
$table->unsignedBigInteger('parent_id')->nullable()->after('id');
$table->foreign('parent_id')->references('id')->on('comments')->onDelete('cascade');
});
parent_id
is nullable
because top-level comments won’t have a parent.
The foreign key points back to the id
of the same comments
table.
2- Define relationships in the comment model
In your Comment
model, add:
public function parent()
{
return $this->belongsTo(Comment::class, 'parent_id');
}
public function replies()
{
return $this->hasMany(Comment::class, 'parent_id');
}
This gives you these collections:
$comment->parent
: the comment this is replying to.$comment->replies
: all comments replying to this one.
3- You can fetch comments with replies in this way;
$comments = Comment::where('post_id', $post->id)
->whereNull('parent_id')
->with('replies')
->get();
With condition:
4- You can display nested replies in blade template;
@foreach ($comments as $comment)
<div class="comment">
<p>{{ $comment->body }}</p>
@foreach ($comment->replies() as $childComment)
<div class="pl-8 bg-gray-200 p-4 rounded">
<p class="font-bold">{{ $childComment->name }}</p>
<p>{{ $childComment->content }}</p>
</div>
@endforeach
</div>
@endforeach
Final notes:
- No need for an extra table.
- Easy to extend to unlimited nested replies.
- Cleaner and more maintainable Eloquent relationships.
- Works well with eager loading (
with()
).