Join WhatsApp ChannelJoin Now

Laravel 12: Step-by-Step Guide to Comment System with Replies

Hi Dev,

People can chat with you and each other. That makes them feel part of something, and they’re more likely to return. When readers comment, they spend more time on your page. They may even come back to reply. That’s good for your blog’s stickiness. Comments tell you what readers liked, didn’t get, or want more of—great sparks for new posts. Readers’ comments add new words and ideas to your posts. This can help search engines see your page as more valuable.

We’ll begin by adding Laravel UI to set up basic user authentication, including registration and login screens. Next, we’ll build a posts table with title and body fields to allow users to create content. Once users are registered, they’ll be able to publish posts that other users can view. We’ll enable commenting on posts, and also support replies to those comments for nested discussions. To manage these relationships, we’ll utilize Laravel’s Eloquent methods—hasMany() for one-to-many and belongsTo() for the inverse connection. Follow along through each step to implement this fully functional, user-driven content system with nested commenting!

Step for Creating Comment System in Laravel 12
Step 1: Install Laravel 12
Step 2: Create Auth using Scaffold
Step 3: Create Posts and Comments Tables
Step 4: Create Models
Step 5: Create Routes
Step 6: Create Controller
Step 7: Create and Update Blade Files
Run Laravel App

Install Laravel 12

For Installing Laravel application 12 run below command. let’s start:

composer create-project laravel/laravel example-app

Create Auth using Scaffold

Now, we will create an auth scaffold command to generate login, register, and dashboard functionalities. So, run the below commands:

Laravel 12 UI Package:

composer require laravel/ui 

Generate Auth:

php artisan ui bootstrap --auth 
npm install
npm run build

Create Posts and Comments Tables

For Creating Posts and Comments tables run below command.

php artisan make:migration create_posts_comments_table

now, let’s update the following migrations table:
database/migrations/2024_06_19_140622_create_posts_comments_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });

        Schema::create('comments', function (Blueprint $table) {
            $table->id();
            $table->integer('user_id')->unsigned();
            $table->integer('post_id')->unsigned();
            $table->integer('parent_id')->unsigned()->nullable();
            $table->text('body');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
        Schema::dropIfExists('comments');
    }
};

now, run the following command:

php artisan migrate

Create Models

To implement a comment system in Laravel, you’ll need to create Post and Comment models, update the User model, also we will right relationship and some model function.

php artisan make:model Post

Now, With hasMany() relationship we will update the model file.

app/Models/Post.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['title', 'body'];
   
    /**
     * The has Many Relationship
     *
     * @var array
     */
    public function comments()
    {
        return $this->hasMany(Comment::class)->whereNull('parent_id')->latest();
    }
}

app/Models/Comment.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    use HasFactory;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['user_id', 'post_id', 'parent_id', 'body'];
   
    /**
     * The belongs to Relationship
     *
     * @var array
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }
   
    /**
     * The has Many Relationship
     *
     * @var array
     */
    public function replies()
    {
        return $this->hasMany(Comment::class, 'parent_id');
    }
}

Create Routes

Now, we need to create some routes. So open your “routes/web.php” file and add the following route.
routes/web.php

<?php

use Illuminate\Support\Facades\Route;

use App\Http\Controllers\PostController;

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

Route::middleware('auth')->group(function () {
    Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
    Route::post('/posts', [PostController::class, 'store'])->name('posts.store');
    Route::get('/posts/{id}', [PostController::class, 'show'])->name('posts.show');
    Route::post('/posts/comment/store', [PostController::class, 'commentStore'])->name('posts.comment.store');
});

Create Controller

In this step, We create a new controoler, PostController. So let’s put the code below.
app/Http/Controllers/PostController.php

<?php
   
namespace App\Http\Controllers;
   
use Illuminate\Http\Request;
use App\Models\Post;
use App\Models\Comment;
   
class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $posts = Post::latest()->get();
    
        return view('posts.index', compact('posts'));
    }
    
    /**
     * Write code on Method
     *
     * @return response()
     */
    public function store(Request $request)
    {
        $this->validate($request, [
             'title' => 'required',
             'body' => 'required'
        ]);
   
        $post = Post::create([
            'title' => $request->title,
            'body' => $request->body
        ]);
   
        return back()->with('success','Post created successfully.');
    }
    
    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $post = Post::find($id);
        return view('posts.show', compact('post'));
    }

    /**
     * Write code on Method
     *
     * @return response()
     */
    public function commentStore(Request $request)
    {
        $request->validate([
            'body'=>'required',
        ]);
   
        $input = $request->all();
        $input['user_id'] = auth()->user()->id;
    
        Comment::create($input);
   
        return back()->with('success','Comment added successfully.');
    }
}

Create and Update Blade Files

Now, we will update app.blade.php file and create posts.blade file. so, let’s do it.

resources/views/layouts/app.blade.php

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.bunny.net">
    <link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">

    <!-- Scripts -->
    @vite(['resources/sass/app.scss', 'resources/js/app.js'])

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />

    <style type="text/css">
        .img-user{
            width: 40px;
            border-radius: 50%;
        }
        .col-md-1{
            padding-right: 0px !important;
        }
        .img-col{
            width: 5.33% !important;
        }
    </style>
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    Laravel Comment System Example - ItSolutionStuff.com
                </a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav me-auto">

                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ms-auto">
                        <!-- Authentication Links -->
                        @guest
                            @if (Route::has('login'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                                </li>
                            @endif

                            @if (Route::has('register'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                </li>
                            @endif
                        @else
                            <li class="nav-item">
                                <a class="nav-link" href="{{ route('posts.index') }}">{{ __('Posts') }}</a>
                            </li>
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }}
                                </a>

                                <div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            @yield('content')
        </main>
    </div>
</body>
</html>

resources/views/posts/index.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header"><i class="fa fa-list"></i> {{ __('Posts List') }}</div>

                <div class="card-body">

                    @session('success')
                        <div class="alert alert-success" role="alert"> 
                            {{ $value }}
                        </div>
                    @endsession
                    
                    <p><strong>Create New Post</strong></p>
                    <form method="post" action="{{ route('posts.store') }}" enctype="multipart/form-data">
                        @csrf
                        <div class="form-group">
                            <label>Title:</label>
                            <input type="text" name="title" class="form-control" />
                            @error('title')
                                <div class="text-danger">{{ $message }}</div>
                            @enderror
                        </div>
                        <div class="form-group">
                            <label>Body:</label>
                            <textarea class="form-control" name="body"></textarea>
                            @error('body')
                                <div class="text-danger">{{ $message }}</div>
                            @enderror
                        </div>
                        <div class="form-group mt-2">
                            <button type="submit" class="btn btn-success btn-block"><i class="fa fa-save"></i> Submit</button>
                        </div>
                    </form>

                    <p class="mt-4"><strong>Post List:</strong></p>
                    
                    @foreach($posts as $post)
                        <div class="card mt-2">
                          <div class="card-body">
                            <h5 class="card-title">{{ $post->title }}</h5>
                            <p class="card-text">{{ $post->body }}</p>
                            <div class="text-end">
                                <a href="{{ route('posts.show', $post->id) }}" class="btn btn-primary">View</a>
                            </div>
                          </div>
                        </div>
                    @endforeach

                </div>
            </div>
        </div>
    </div>
</div>
@endsection

resources/views/posts/show.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header"><h1><i class="fa fa-thumbs-up"></i> {{ $post->title }}</h1></div>

                <div class="card-body">

                    @session('success')
                        <div class="alert alert-success" role="alert"> 
                            {{ $value }}
                        </div>
                    @endsession

                    {{ $post->body }}

                    <h4 class="mt-4">Comments:</h4>

                    <form method="post" action="{{ route('posts.comment.store') }}">
                        @csrf
                        <div class="form-group">
                            <textarea class="form-control" name="body" placeholder="Write Your Comment..."></textarea>
                            <input type="hidden" name="post_id" value="{{ $post->id }}" />
                        </div>
                        <div class="form-group text-end">
                            <button class="btn btn-success mt-2"><i class="fa fa-comment"></i> Add Comment</button>
                        </div>
                    </form>
                    
                    <hr/>
                    @include('posts.comments', ['comments' => $post->comments, 'post_id' => $post->id])
                    
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

resources/views/posts/comments.blade.php

@foreach($comments as $comment)
    <div class="display-comment row mt-3" @if($comment->parent_id != null) style="margin-left:40px;" @endif>
        <div class="col-md-1 text-end img-col">
            <img src="https://randomuser.me/api/portraits/men/43.jpg" class="img-user">
        </div>
        <div class="col-md-11">
            <strong>{{ $comment->user->name }}</strong>
            <br/><small><i class="fa fa-clock"></i> {{ $comment->created_at->diffForHumans() }}</small>
            <p>{!! nl2br($comment->body) !!}</p>
            <form method="post" action="{{ route('posts.comment.store') }}">
                @csrf
                <div class="row">
                    <div class="col-md-11">
                        <div class="form-group">
                            <textarea class="form-control" name="body" placeholder="Write Your Reply..." style="height: 40px;"></textarea>
                            <input type="hidden" name="post_id" value="{{ $post_id }}" />
                            <input type="hidden" name="parent_id" value="{{ $comment->id }}" />
                        </div>
                    </div>
                    <div class="col-md-1">
                        <button class="btn btn-warning"><i class="fa fa-reply"></i> Reply</button>
                    </div>
                </div>
            </form>
            @include('posts.comments', ['comments' => $comment->replies])
        </div>
    </div>
@endforeach

Run Laravel App:

php artisan serve

I hope it will assist you…

Recommended Posts