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.
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:
- 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.
- Keeping user-uploaded files outside of the
- 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.
- 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
- 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 thepublic/storage
directory.
- The symbolic link makes it easy to access files stored in
- 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.
- This approach ensures that your project is portable across different environments. When deploying to production, you can run the
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!