Laravel 11 Create Comment System with Replies Example

Jun 23, 2024 | Laravel 11 Laravel


Hello Dev,

A comment system is essential for blog and tutorial websites as it allows readers to share thoughts, ask questions, and interact with both the author and other readers. It fosters community, provides valuable feedback, and enhances engagement on the site. Comments enrich content by making it more dynamic and engaging, thereby improving the overall reader experience and encouraging return visits. Additionally, comments offer authors insights into their audience's interests and concerns, aiding in the creation of better content.

In this example, we'll start by setting up Laravel UI to establish basic authentication functionalities like login and registration. Next, we'll create a posts table with fields for title and body content. Users will be able to register, create posts, and view posts made by others. They can also add comments, with an additional feature allowing users to reply to comments. Leveraging Laravel's relationships such as hasMany() and belongsTo(), we'll ensure the comment system functions seamlessly.

Follow the steps outlined below to implement this example:

1. Install Laravel UI for user authentication.

2. Create a database table for posts, including fields for title and body.

3. Implement user registration and post creation functionality.

4. Enable viewing posts and adding comments with reply functionality.

5. Utilize Laravel relationships (hasMany() and belongsTo()) to manage comments effectively.

By following these steps, you'll be able to integrate a robust comment system into your Laravel 11 application, enhancing user interaction and content engagement.

Step 1: Install Laravel 11

Step 2: Scaffold Authentication

Step 3: Create Posts and Comments Tables

Step 4: Define Models

Step 5: Set Up Routes

Step 6: Develop Controllers

Step 7: Create and Modify Blade Templates

Step 8: Run Your Laravel Application

Following these steps will guide you through implementing a fully functional comment system in your Laravel 11 application.

Step 1: Install Laravel 10

If you haven't already, create a new Laravel project:

composer create-project laravel/laravel example-app
Step 2: Scaffold Authentication

Use Laravel UI to scaffold authentication views and routes:

composer require laravel/ui
php artisan ui bootstrap --auth
npm install && npm run dev
Read Also: Laravel 11 Database Notifications Example Tutorial Step 3: Create Posts and Comments Tables

Generate migrations for posts and comments tables:

php artisan make:migration create_posts_comments_table
Comments Table Migration:
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');
    }
};

Run the migrations:

php artisan migrate
Step 4: Define Models

Create models for Post and Comment. Define relationships where needed. For instance, in the Post model, you might define a one-to-many relationship with Comment:

php artisan make:model Post

now, update the model file with hasMany() relationship:

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');
    }
}
Read Also: How to Install and Use Laravel 11 Debugbar Package? Step 5: Set Up Routes

Define routes for creating posts, commenting on posts, and replying to comments in routes/web.php:

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');
});
Step 6: Develop Controllers

Generate a controller for Post:

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.');
    }
}
Read Also: How to Create Like Dislike Button in Laravel 11? Step 7: Create and Modify Blade Templates

Create views for listing posts, adding comments, and displaying comments. Use Blade syntax to render dynamic content based on user actions and relationships between posts and comments.

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 - ItErrorSolution.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
Step: Run the Application

Now, to run the Laravel application, please type the following command and press enter:

php artisan serve

Now, open your web browser and enter the provided URL to view the output of the application.

View Your Application
http://localhost:8000/

Thank you for your encouragement! If you have any questions or need further assistance, feel free to ask. I'm here to help!



Tags :
#Laravel 11
#Laravel
ItErrorSolution.com

ItErrorSolution.com

"Hey there! I'm a full-stack developer and proud owner of ItErrorSolution.com, based right here in India. I love nothing more than sharing handy tips and tricks with my fellow developers through easy-to-follow tutorials. When it comes to coding, I'm all about PHP, Laravel, Angular, Vue, Node, JavaScript, jQuery, CodeIgniter, and Bootstrap – been hooked on them forever! I'm all about putting in the work and staying committed. Ready to join me on this journey to coding?"