CRUD Laravel using Modal Bootstrap for Beginners

In this tutorial, I will guide you through creating a simple Laravel project that performs basic CRUD (Create, Read, Update, Delete) operations using a Bootstrap modal. This guide is intended for those who are new to Laravel and covers everything from setting up the project to building the functionality.

CRUD Laravel using Modal Bootstrap for Beginners

Step 1: Setting Up a New Laravel Project

First, we need to create a new Laravel project. If you don’t have Composer installed, install it first from getcomposer.org.

composer create-project --prefer-dist laravel/laravel laravel-crud-modal
cd laravel-crud-modal

Next, set up your environment by configuring the .env file for database connection:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_crud_modal
DB_USERNAME=root
DB_PASSWORD=yourpassword

Make sure to create the database with the name laravel_crud_modal before proceeding.

Step 2: Creating the Model and Migration

Create a model named Data along with its migration file:

php artisan make:model Data -m

Now, update the migration file located in database/migrations/xxxx_xx_xx_create_data_table.php as follows:

public function up(): void
{
    Schema::create('data', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('photo');
        $table->timestamps();
    });
}

Run the migration to create the table in your database:

php artisan migrate

Step 3: Creating the Controller

Create a controller named DataController:

php artisan make:controller DataController

Update the DataController.php file located in app/Http/Controllers/ with the following code:

<?php

namespace App\Http\Controllers;

use App\Models\Data;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class DataController extends Controller
{
    public function index()
    {
        $data = Data::all();
        return view('index', compact('data'));
    }

    public function store(Request $request)
    {
        $validatedData = $request->validate([
            'name' => 'required|string|max:255',
            'photo' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048',
        ]);

        if ($request->file('photo')) {
            $fileName = date('YmdHis') . $request->file('photo')->getClientOriginalName();
            $request->file('photo')->storeAs('public/uploads', $fileName);
            $validatedData['photo'] = $fileName;
        }

        Data::create($validatedData);

        return redirect()->route('data.index')->with('success', 'Data has been added');
    }

    public function edit($id)
    {
        $data = Data::findOrFail($id);
        return response()->json($data);
    }

    public function update(Request $request, $id)
    {
        $data = Data::findOrFail($id);

        $validatedData = $request->validate([
            'name' => 'required|string|max:255',
            'photo' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
        ]);

        if ($request->file('photo')) {
            Storage::delete('public/uploads/' . $data->photo);

            $fileName = date('YmdHis') . $request->file('photo')->getClientOriginalName();
            $request->file('photo')->storeAs('public/uploads', $fileName);
            $validatedData['photo'] = $fileName;
        }

        $data->update($validatedData);

        return redirect()->route('data.index')->with('success', 'Data has been updated');
    }

    public function destroy($id)
    {
        $data = Data::findOrFail($id);
        Storage::delete('public/uploads/' . $data->photo);
        $data->delete();

        return redirect()->route('data.index')->with('success', 'Data has been deleted');
    }
}

Step 4: Setting Up Routes

In the routes/web.php file, define the routes for the CRUD operations:

use App\Http\Controllers\DataController;

Route::get('/', [DataController::class, 'index'])->name('data.index');
Route::post('/data', [DataController::class, 'store'])->name('data.store');
Route::get('/data/{id}/edit', [DataController::class, 'edit'])->name('data.edit');
Route::put('/data/{id}', [DataController::class, 'update'])->name('data.update');
Route::delete('/data/{id}', [DataController::class, 'destroy'])->name('data.destroy');

Step 5: Creating the Views

Create a welcome.blade.php file inside the resources/views/ directory to serve as the main layout:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple CRUD Project</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container mt-5">
       <div class="container">
        <h1>Data List</h1>
        <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
            Add Data
        </button>

        <div class="modal fade" id="addModal">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h4 class="modal-title">Add Data</h4>
                        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                    </div>
                    <div class="modal-body">
                        <form id="addForm" action="{{ route('data.store') }}" method="POST" enctype="multipart/form-data">
                            @csrf
                            <div class="mb-3">
                                <label for="name" class="form-label">Name</label>
                                <input type="text" class="form-control" id="name" name="name" required>
                            </div>
                            <div class="mb-3">
                                <label for="photo" class="form-label">Photo</label>
                                <input type="file" class="form-control" id="photo" name="photo" required>
                            </div>
                            <button type="submit" class="btn btn-primary">Save</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>

        <table class="table mt-3" id="dataTable">
            <thead>
                <tr>
                    <th>No</th>
                    <th>Name</th>
                    <th>Photo</th>
                    <th>Action</th>
                </tr>
            </thead>
            <tbody>
                @foreach ($data as $item)
                    <tr>
                        <td>{{ $loop->iteration }}</td>
                        <td>{{ $item->name }}</td>
                        <td><img src="{{ asset('storage/uploads/' . $item->photo) }}" alt="Photo" width="50"></td>
                        <td>
                            <button class="btn btn-warning edit-btn" data-id="{{ $item->id }}" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
                            <form action="{{ route('data.destroy', $item->id) }}" method="POST" style="display:inline-block;">
                                @csrf
                                @method('DELETE')
                                <button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure?')">Delete</button>
                            </form>
                        </td>
                    </tr>
                @endforeach
            </tbody>
        </table>

        <div class="modal fade" id="editModal">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h4 class="modal-title">Edit Data</h4>
                        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                    </div>
                    <div class="modal-body">
                        <form id="editForm" action="#" method="POST" enctype="multipart/form-data">
                            @csrf
                            @method('PUT')
                            <input type="hidden" id="editId" name="id">
                            <div class="mb-3">
                                <label for="editName" class="form-label">Name</label>
                                <input type="text" class="form-control" id="editName" name="name" required>
                            </div>
                            <div class="mb-3">
                                <label for="editPhoto" class="form-label">Photo</label>
                                <input type="file" class="form-control" id="editPhoto" name="photo">
                            </div>
                            <button type="submit" class="btn btn-primary">Update</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function () {
            const editModal = new bootstrap.Modal(document.getElementById('editModal'));
            const editButtons = document.querySelectorAll('.edit-btn');

            editButtons.forEach(button => {
                button.addEventListener('click', function () {
                    const id = this.getAttribute('data-id');

                    fetch(`/data/${id}/edit`)
                        .then(response => response.json())
                        .then(data => {
                            document.getElementById('editId').value = data.id;
                            document.getElementById('editName').value = data.name;
                            document.getElementById('editForm').action = `/data/${data.id}`;
                        })
                        .catch(error => console.error('Error:', error));
                });
            });
        });
    </script>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

Step 6: If image not showing

php artisan storage:link

The command php artisan storage:link is used in Laravel to create a symbolic link from the public/storage directory to the storage/app/public directory. This symbolic link is essential for making files stored in the storage/app/public directory accessible via the web.

Why Use a Symbolic Link?

Laravel stores uploaded files, such as images, in the storage/app/public directory by default. However, this directory is not publicly accessible, meaning that files stored here cannot be accessed directly through a web URL. To solve this, Laravel provides a mechanism to link this directory to the public/storage directory using a symbolic link.

Key Reasons:

  1. Security:
    • Keeping user-uploaded files outside of the public directory improves security by preventing direct access to sensitive files. Only the files that need to be accessible via the web are linked.
  2. Separation of Concerns:
    • Laravel’s design philosophy encourages separating publicly accessible files from those that are not meant to be directly accessed by users. By storing files in the storage directory and linking only what’s necessary, you maintain a clean and organized file structure.
  3. Ease of Access:
    • The symbolic link makes it easy to access files stored in storage/app/public through URLs, as if they were stored directly in the public/storage directory.
  4. Portability:
    • This approach ensures that your project is portable across different environments. When deploying to production, you can run the php artisan storage:link command to create the necessary links, ensuring that file access works consistently across all environments.

In this tutorial, you learned how to create a simple CRUD application using Laravel with a Bootstrap modal for user-friendly interactions. The key aspects covered include setting up a new Laravel project, creating a model and migration, building a controller with CRUD operations, and creating views to display and manage data.

With this foundation, you can further explore Laravel’s powerful features and customize the project according to your needs. Happy coding!

Scroll to Top