Introduction
CRUD (Create, Read, Update, Delete) operations form the foundation of most web applications. Laravel makes implementing these operations elegant and efficient through its powerful Eloquent ORM, Resource Controllers, Blade templating, and built-in validation.
In this comprehensive tutorial, we'll build a Product Management System — a practical example where you can create, view, edit, and delete products with features like:
- Form validation
- Image upload and storage
- Pagination
- Success/error messages
- Bootstrap 5 styling for a clean UI
This guide works with Laravel 11 or later (including Laravel 12/13 concepts as of 2026). We'll use the latest best practices.
Prerequisites
- PHP 8.2+, Composer, MySQL/MariaDB (or another supported database), Basic knowledge of PHP and MVC pattern
Step 1: Install Laravel
Open your terminal and run:
composer create-project laravel/laravel laravel-crud-app
cd laravel-crud-appStart the development server later with `php artisan serve`.
Step 2: Configure the Database
Update your `.env` file with database credentials:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_crud
DB_USERNAME=root
DB_PASSWORD=Create the database in MySQL and run:
php artisan migrateStep 3: Create Model, Migration, and Resource Controller
We'll create a Product model with migration and a full Resource Controller in one go:
php artisan make:model Product -mcrThe `-mcr` flag generates:
- Model (`app/Models/Product.php`)
- Migration (`database/migrations/xxxx_xx_xx_create_products_table.php`)
- Resource Controller (`app/Http/Controllers/ProductController.php`)
Step 4: Define the Products Table (Migration)
Open the migration file and update the `up()` method:
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->decimal('price', 10, 2);
$table->string('image')->nullable();
$table->timestamps();
});
}Run the migration:
php artisan migrateStep 5: Update the Product Model
In `app/Models/Product.php`, add mass assignment protection:
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
protected $fillable = [
'name',
'description',
'price',
'image',
];
}Step 6: Set Up Resource Routes
Open `routes/web.php` and register the resource route:
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;
Route::resource('products', ProductController::class);This single line creates all 7 RESTful routes:
- `GET /products` → index
- `GET /products/create` → create
- `POST /products` → store
- `GET /products/{product}` → show
- `GET /products/{product}/edit` → edit
- `PUT/PATCH /products/{product}` → update
- `DELETE /products/{product}` → destroy
Step 7: Create Form Request for Validation (Optional but Recommended)
Generate validation requests:
php artisan make:request StoreProductRequest
php artisan make:request UpdateProductRequestIn `app/Http/Requests/StoreProductRequest.php`:
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'price' => 'required|numeric|min:0',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
];
}Do the same for `UpdateProductRequest` (make `image` optional for updates).
Step 8: Implement the Resource Controller
Open `app/Http/Controllers/ProductController.php` and fill in the methods:
namespace App\Http\Controllers;
use App\Models\Product;
use App\Http\Requests\StoreProductRequest;
use App\Http\Requests\UpdateProductRequest;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Request;
class ProductController extends Controller
{
public function index()
{
$products = Product::latest()->paginate(10);
return view('products.index', compact('products'));
}
public function create()
{
return view('products.create');
}
public function store(StoreProductRequest $request)
{
$data = $request->validated();
if ($request->hasFile('image')) {
$data['image'] = $request->file('image')->store('products', 'public');
}
Product::create($data);
return redirect()->route('products.index')
->with('success', 'Product created successfully!');
}
public function show(Product $product)
{
return view('products.show', compact('product'));
}
public function edit(Product $product)
{
return view('products.edit', compact('product'));
}
public function update(UpdateProductRequest $request, Product $product)
{
$data = $request->validated();
if ($request->hasFile('image')) {
if ($product->image) {
Storage::disk('public')->delete($product->image);
}
$data['image'] = $request->file('image')->store('products', 'public');
}
$product->update($data);
return redirect()->route('products.index')
->with('success', 'Product updated successfully!');
}
public function destroy(Product $product)
{
if ($product->image) {
Storage::disk('public')->delete($product->image);
}
$product->delete();
return redirect()->route('products.index')
->with('success', 'Product deleted successfully!');
}
}Note: We're using **Route Model Binding** (`Product $product`) for clean code.
Step 9: Create Blade Views
Create the `resources/views/products/` directory and add these files.
`index.blade.php` (List with Pagination)
@extends('layouts.app')
@section('content')
Products
Create New Product
@if (session('success'))
{{ session('success') }}
@endif
ID
Image
Name
Price
Actions
@foreach($products as $product)
{{ $product->id }}
@if($product->image)
@endif
{{ $product->name }}
${{ number_format($product->price, 2) }}
View
Edit
@endforeach
{{ $products->links() }}
@endsection`create.blade.php` and `edit.blade.php`
Use similar forms. Here's a basic structure for `create.blade.php`:
@extends('layouts.app')
@section('content')
Create Product
@endsectionFor `edit.blade.php`, use `@method('PUT')` and populate values with `$product->name`, etc.
`show.blade.php`
Simple detail view displaying all product info and image.
Step 10: Layout and Bootstrap
Publish or create a basic `resources/views/layouts/app.blade.php` with Bootstrap 5 CDN for quick styling:
Laravel CRUD
@yield('content')
For better styling, install Laravel Breeze or use Vite with Bootstrap.