#AJAX With Laravel API and More - Learn Laravel Beyond the Limit

Hello, Everyone again with Laravel Advanced Topics Tutorial. Today, I'll show you how we can implement AJAX in our laravel web application.

 

What is AJAX:

AJAX is Asynchronous JavaScript And XML and AJAX is not a programming language. It's uses some combination of Browser and HTML DOM.

Browser - Send XMLHttpRequest Object to server

HTML DOM - Display data instantly on browser

 

How to use AJAX in Laravel Application:

AJAX is very simple with Laravel integration. Steps:

  1. Install Jquery Library
  2. Make some API's through Laravel
  3. Call that API inside ajax call. like - $.ajax({ //.... });
  4. Send data to server 
  5. Process data in the Server
  6. Display in the browser after getting the data

 

Let's start....

 

For Base Project we'll use this main branch - https://github.com/ManiruzzamanAkash/Laravel-Advanced-Topics

Where already - We have a Post model and seeder setup

 

What we'll do today with AJAX example:

  1. Display all the comments through AJAX
  2. Send a Comment to the server through AJAX
  3. Display Validation on the Browser DOM
  4. Refresh data with Reload after creating Comment
  5. & Many more concepts...

 

Create New Model, Migration, Seeder for Post Comment

 

Create Migration and model

php artisan make:model Comment -m

In migration file - Just add a comment and post_id field as new.

<?php

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

class CreateCommentsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->id();
            $table->text('comment');
            $table->foreignId('post_id')
                ->constrained()
                ->onUpdate('cascade')
                ->onDelete('cascade');
            $table->timestamps();
        });
    }

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

 

In model - Models/Comment.php file just implement basic fillable, as we'll create comment later.

<?php

namespace App\Models;

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

class Comment extends Model
{
    use HasFactory;

    protected $fillable = ['comment', 'post_id'];
}

 

Also make Post model relationed with Comment model - Post has Many comments. 1 to Many relationship using Eloquent.

<?php

namespace App\Models;

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

class Post extends Model
{
    use HasFactory;

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

 

In factories file - database/factories 

<?php

namespace Database\Factories;

use App\Models\Comment;
use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;

class CommentFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Comment::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        $post_ids = Post::pluck('id')->toArray();

        return [
            'comment' => $this->faker->text(),
            'post_id' => $post_ids[array_rand($post_ids)]
        ];
    }
}

 

Then setup is done. Just run the command to migrate the seeder with fresh migration - 

php artisan migrate:fresh --seed

So, our posts and comments table will be all set and ready to start view post on browser using just Laravel.

 

Add Routes: in routes/web.php

<?php

use App\Http\Controllers\PostsController;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', [PostsController::class, 'index'])->name('index');
Route::get('/{slug}', [PostsController::class, 'show'])->name('index.show');

 

In PostsController: Just add two routes functionality

<?php

namespace App\Http\Controllers;

use App\Models\Post;

class PostsController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $posts = Post::with('comments')->paginate(20);
        return view('index', compact('posts'));
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $post = Post::find($id);
        return view('show', compact('post'));
    }
}


We'll create a main.js file now inside public/js/main.js

Now - Files would be - 

Posts List: views/index.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">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
    <title>AJAX Example</title>
</head>

<body>
    <div class="row justify-content-center">
        <div class="col-md-8 mt-5">
            <h2>Posts</h2>
            @foreach ($posts as $post)
                <div class="card card-body mt-2">
                    <h2> {{ $post->title }} </h2>
                    <div>
                        {!! $post->description !!}
                    </div>
                    <a href="{{ route('index.show', $post->id) }}">
                        View
                    </a>
                </div>
            @endforeach
        </div>
    </div>

    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous">
    </script>
</body>

</html>

 

Post Detail would be - 

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
        integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
    <title>AJAX Example</title>
</head>

<body>
    <div class="container row justify-content-center mb-5" id="post-detail" data-post-id="{{ $post->id }}">
        <div class="col-md-8 mt-5 card card-body">
            <h2>{{ $post->title }}</h2>
            <div>
                {!! $post->description !!}
            </div>
        </div>

        <div class="mt-2">
            <h3>Comments</h3>

            <div id="post-comments">
                Loading Comments...
                <i class="fa fa-spin fa-spinner" aria-hidden="true"></i>
            </div>

            <h3 class="mt-5">New Comment</h3>

            <div class="alert alert-success visually-hidden" id="successMessage">Comments saved successfully !!</div>
            <form action="post" id="comment-submit-form">
                @csrf
                <input type="hidden" name="post_id" id="post-id-comment" type="hidden" value="{{ $post->id }}">
                <textarea name="comment" id="comment-input" cols="30" rows="4" class="form-control"
                    placeholder="Enter your comment message in 500 characters"></textarea>

                <div id="comment-errors-data" class="text-danger"></div>
                <br>
                <button class="btn btn-success" type="submit" id="comment-submit-button">
                    Save
                </button>
            </form>
        </div>
    </div>

    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous">
    </script>
    <script src="{{ asset('js/main.js') }}"></script>

    <script>
        // getCommentsOfPosts({{ $post->id }});
    </script>
</body>

</html>

 

API Routes:

routes/api.php

<?php

use App\Http\Controllers\CommentsController;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::get('/comments/{id}', [CommentsController::class, 'index'])->name('comments.index');
Route::post('/comments/{id}', [CommentsController::class, 'store'])->name('comments.store');

 

CommentsController to handle API's - app\Http\Controllers\CommentsController.php

<?php

namespace App\Http\Controllers;

use App\Models\Comment;
use Illuminate\Http\Request;

class CommentsController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index($id)
    {
        $comments = Comment::where('post_id', $id)->orderBy('id', 'desc')->get();
        return view('templates.comments', compact('comments'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $request->validate(
            [
                'comment' => 'required'
            ],
            [
                'comment.required' => 'Please give comment'
            ]
        );

        $comment = Comment::create([
            'comment' => $request->comment,
            'post_id' => $request->post_id
        ]);

        return $comment;
    }
}

 

Add templates view for getting comments- in templates/comments.blade.php

@foreach ($comments as $comment)
    <div class="card card-body m-2">
        {{ $comment->comment }}
        <p>
            <span class="badge badge-info">at {{ $comment->created_at->diffForHumans() }}</span>
        </p>
    </div>
@endforeach

@if (!count($comments))
    <div class="bg-warning p-4">
        No Comments found...
    </div>
@endif

 

Main.js File for ajax actions - 

$(document).ready(function () {
    const post_id = $("#post-detail").attr("data-post-id");
    getCommentsOfPosts(post_id)

    $("#comment-submit-form").submit(function(e){
        e.preventDefault();
        const formData     = $(this);
        const submitButton = $("#comment-submit-button");
        const post_id      = $("#post-id-comment").val();

        submitButton.html('Saving....<i class="fa fa-spin fa-spinner" aria-hidden="true"></i>');

        $.ajax({
            method: "POST",
            url: "/api/comments/" + post_id,
            data: formData.serialize(),
            success: (result) => {
                submitButton.html('Save');
                $("#comment-errors-data").html('');
                $("#comment-input").val('');
                $("#successMessage").removeClass('visually-hidden');
                getCommentsOfPosts(post_id);
            },
            error: (error) => {
                if(error.status === 422) { // "Unprocessable Entity" - Form data invalid
                    $("#successMessage").addClass('visually-hidden');
                    var message = error.responseJSON.errors ? error.responseJSON.errors.comment ?  error.responseJSON.errors.comment[0] : '' : '';
                    $("#comment-errors-data").html(message);
                    submitButton.html('Save');
                }
            }
        });
    });

});

function getCommentsOfPosts(post_id) {
    if( typeof post_id !== 'undefined' && post_id !== '' && !isNaN(post_id)) {
        $.ajax({
            method: "GET",
            url: "/api/comments/" + post_id,
            success: (result) => {
                $("#post-comments").html(result);
            },
            error: (error) => {
                alert('Something went wrong to fetch datas...');
            }
        });
    }
}


function getPosts() {
    $.ajax({
        method: "GET",
        url: "/api/posts",
        success: (result) => {
            $("#posts").html(result);
        },
        error: (error) => {
            alert('Something went wrong to fetch datas...');
        }
    });
}

 

It's done.

  1. Lets check in browser with some posts lists
  2. In detail posts, comments will be loaded in frontend part
  3. Someone can create comment instantly with comment reloading...

 

Thanks for viewing the Laravel ajax implementation way.