Laravel 8/7: Ajax CRUD Tutorial using jQuery & Bootstrap

By Parth Patel on Oct 25, 2020

In previous post, we talked about how to install Bootstrap 4 instead of Tailwind CSS in Laravel 8 . Today, we will learn about how to use ajax requests in Laravel by building simple To-Do CRUD application functioning entirely via ajax requests in Laravel 8.

Since, this article is sequel to our Laravel 8 Bootstrap Installation tutorial, you should definitely check it out, if you want to learn how to install Bootstrap in Laravel 8.

So, now that we have new laravel project with bootstrap and auth installed, let's get started.

1) Create Database Migration

Let's create a database migration for our todos:

php artisan make:migration create_todos_table --create=todos

Now, we should update our newly created migration file by adding required columns as shown below:

public function up()
{
    Schema::create('todos', function (Blueprint $table) {
        $table->id();
        $table->string('todo');
        $table->timestamps();
    });
}

Now, run the migration:

php artisan migrate

2) Create Model

Now let's create model for our todos:

php artisan make:model Todo

This will create Todo model file in app/models folder

3) Create Routes

Let's create new routes for our CRUD application in routes/web.php file:

<?php

use Illuminate\Support\Facades\Route;

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

Auth::routes();

Route::get('/home', [App\Http\Controllers\TodoController::class, 'index']);
Route::post('/todos/create', [App\Http\Controllers\TodoController::class, 'store']);
Route::put('/todos/{todo}', [App\Http\Controllers\TodoController::class, 'update']);
Route::delete('/todos/{todo}', [App\Http\Controllers\TodoController::class, 'destroy']);

Here, we are connecting routes to TodoController which we will create in next step.

4) Create Controller

Let's create controller file to handle CRUD requests related to todos.

php artisan make:controller TodoController

Now, let's add required methods to handle CRUD requests:

<?php

namespace App\Http\Controllers;

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

class TodoController extends Controller
{
    public function index()
    {
        $todos = Todo::all();
        return view('home', compact('todos'));
    }

    public function store(Request $request)
    {
        $request->validate([
            'todo' => 'required',
            ]);

        $todo = new Todo();
        $todo->todo = $request->todo;
        $todo->save();
        return Response::json($todo);
    }

    public function update(Todo $todo, Request $request)
    {
        $request->validate([
            'todo' => 'required',
            ]);

        $todo->todo = $request->todo;
        $todo->save();
        return Response::json($todo);
    }

    public function destroy(Todo $todo)
    {
        $todo->delete();
        return Response::json($todo);
    }
}

Here, we have created functions for each type of request to handle - Index (For listing todos), Store(creating new todo), Update (updating existing todo), Destroy (deleting todo).

5) Create Blade View files

Now, the last critical part is to create User Interface for our to-do application. We will need to modify only one blade file - home.blade.php, since all other operations will be handled through ajax using Bootstrap model forms.

Cool, right?

Okay, let's get started

Update your home.blade.php file located in /resources/views folder. You can create new file too but in that case, you will have to use that new view file in index controller method above.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="card">
        <div class="card-body">
            <div class="row">
            <div class="col-12 text-right">
            <button type="button" class="btn btn-success" data-toggle="modal" data-target="#addTodoModal">Add Todo</button>
            </div>
            </div>
            <div class="row" style="clear: both;margin-top: 18px;">
                <div class="col-12">
                <table class="table table-striped table-bordered">
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>Todo</th>
                            <th>Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach($todos as $todo)
                        <tr id="todo_{{$todo->id}}">
                            <td>{{ $todo->id  }}</td>
                            <td>{{ $todo->todo }}</td>
                            <td>
                                <a data-id="{{ $todo->id }}" onclick="editTodo(event.target)" class="btn btn-info">Edit</a>
                                <a class="btn btn-danger" onclick="deleteTodo({{ $todo->id }})">Delete</a>
                            </td>
                        </tr>
                        @endforeach
                    </tbody>
                </table>
            </div>
            </div>
        </div>
    </div>
    
</div>
<div class="modal fade" id="addTodoModal" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
        <div class="modal-header">
            <h4 class="modal-title">Add Todo</h4>
        </div>
        <div class="modal-body">

                <div class="form-group">
                    <label for="name" class="col-sm-2">Task</label>
                    <div class="col-sm-12">
                        <input type="text" class="form-control" id="task" name="todo" placeholder="Enter task">
                        <span id="taskError" class="alert-message"></span>
                    </div>
                </div>

        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-primary" onclick="addTodo()">Save</button>
        </div>
    </div>
  </div>
  
</div>
<div class="modal fade" id="editTodoModal" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
        <div class="modal-header">
            <h4 class="modal-title">Edit Todo</h4>
        </div>
        <div class="modal-body">

               <input type="hidden" name="todo_id" id="todo_id">
                <div class="form-group">
                    <label for="name" class="col-sm-2">Task</label>
                    <div class="col-sm-12">
                        <input type="text" class="form-control" id="edittask" name="todo" placeholder="Enter task">
                        <span id="taskError" class="alert-message"></span>
                    </div>
                </div>

        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-primary" onclick="updateTodo()">Save</button>
        </div>
    </div>
  </div>
<script>

    function addTodo() {
        var task = $('#task').val();
        let _url     = `/todos`;
        let _token   = $('meta[name="csrf-token"]').attr('content');

        $.ajax({
            url: _url,
            type: "POST",
            data: {
                todo: task,
                _token: _token
            },
            success: function(data) {
                    todo = data
                    $('table tbody').append(`
                        <tr id="todo_${todo.id}">
                            <td>${todo.id}</td>
                            <td>${ todo.todo }</td>
                            <td>
                                <a data-id="${ todo.id }" onclick="editTodo(${todo.id})" class="btn btn-info">Edit</a>
                                <a data-id="${todo.id}" class="btn btn-danger" onclick="deleteTodo(${todo.id})">Delete</a>
                            </td>
                        </tr>
                    `);

                    $('#task').val('');

                    $('#addTodoModal').modal('hide');
            },
            error: function(response) {
                $('#taskError').text(response.responseJSON.errors.todo);
            }
        });
    }

    function deleteTodo(id) {
        let url = `/todos/${id}`;
        let token   = $('meta[name="csrf-token"]').attr('content');

        $.ajax({
            url: url,
            type: 'DELETE',
            data: {
            _token: token
            },
            success: function(response) {
                $("#todo_"+id).remove();
            }
        });
    }

    function editTodo(e) {
        var id  = $(e).data("id");
        var todo  = $("#todo_"+id+" td:nth-child(2)").html();
        $("#todo_id").val(id);
        $("#edittask").val(todo);
        $('#editTodoModal').modal('show');
    }

    function updateTodo() {
        var task = $('#edittask').val();
        var id = $('#todo_id').val();
        let _url     = `/todos/${id}`;
        let _token   = $('meta[name="csrf-token"]').attr('content');

        $.ajax({
            url: _url,
            type: "PUT",
            data: {
                todo: task,
                _token: _token
            },
            success: function(data) {
                    todo = data
                    $("#todo_"+id+" td:nth-child(2)").html(todo.todo);
                    $('#todo_id').val('');
                    $('#edittask').val('');
                    $('#editTodoModal').modal('hide');
            },
            error: function(response) {
                $('#taskError').text(response.responseJSON.errors.todo);
            }
        });
    }

</script>
@endsection

Okay, this is quite long code to explain!

I will separate it into 3 sections:

  1. HTML listing tasks → Here, we are listing all todo tasks via @foreach loop. But, more importantly, I have assigned 3 functions to 3 action buttons → Add, Edit & Delete
  2. Two Models → Add Todo Modal & Edit Todo Modal [ Pretty self explanatory ]
  3. jQuery/JavaScript → This part takes up all the logic. You can see that I have created separate functions and via $.ajax method, I am calling our routes endpoint and performing various operations.

For the beginners, working with ajax requests can be overwhelming, but with time, it gets easier to figure out different techniques and workarounds to achieve what you want.

Adios,