Below is a simple CRUD tutorial in Laravel 13 using a Product example with Blade views. This is a classic Resource Controller + Blade Templates CRUD application. Laravel follows the MVC (Model-View-Controller) pattern:
- Routes → Connect URLs to controller methods
- Model (Product) → Handles database interaction
- Controller (ProductController) → Handles logic and HTTP requests
- Views (Blade templates) → Handle the user interface
1. Create Laravel Project
composer create-project laravel/laravel crud-app
cd crud-app
Configure your database in .env:
DB_DATABASE=crud_app
DB_USERNAME=root
DB_PASSWORD=
Then run:
php artisan migrate
2. Create Model, Migration, and Controller
php artisan make:model Product -m
php artisan make:controller ProductController --resource
3. Create Products Table
Open the migration file in:
database/migrations/xxxx_xx_xx_create_products_table.php
Update it:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
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->integer('stock')->default(0);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('products');
}
};
Run migration:
php artisan migrate
4. Update Product Model
Open:
app/Models/Product.php
Add fillable fields:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = [
'name',
'description',
'price',
'stock',
];
}
5. Add Resource Route
Open:
routes/web.php
Add:
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;
Route::get('/', function () {
return redirect()->route('products.index');
});
Route::resource('products', ProductController::class);
This single line “Route::resource(‘products’, ProductController::class);” automatically creates 7 RESTful routes:
| HTTP Method | URL | Controller Method | Purpose |
|---|---|---|---|
| GET | /products | index() | List all |
| GET | /products/create | create() | Show create form |
| POST | /products | store() | Save new product |
| GET | /products/{product} | show() | View one |
| GET | /products/{product}/edit | edit() | Show edit form |
| PUT/PATCH | /products/{product} | update() | Update product |
| DELETE | /products/{product} | destroy() | Delete product |
There’s also a root redirect to the products index.
6. Create CRUD Controller Logic
Open:
app/Http/Controllers/ProductController.php
Replace with:
namespace App\Http\Controllers;
use App\Models\Product;
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(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'price' => 'required|numeric|min:0',
'stock' => 'required|integer|min:0',
]);
Product::create($request->all());
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(Request $request, Product $product)
{
$request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'price' => 'required|numeric|min:0',
'stock' => 'required|integer|min:0',
]);
$product->update($request->all());
return redirect()
->route('products.index')
->with('success', 'Product updated successfully.');
}
public function destroy(Product $product)
{
$product->delete();
return redirect()
->route('products.index')
->with('success', 'Product deleted successfully.');
}
}
7. Create Blade Views
Create this folder:
resources/views/products
8. Create Layout File
Create:
resources/views/layouts/app.blade.php
Add:
<!DOCTYPE html>
<html>
<head>
<title>Laravel CRUD</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h2 class="mb-4">Laravel Product CRUD</h2>
@if(session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@yield('content')
</div>
</body>
</html>
9. Product Index Page
Create:
resources/views/products/index.blade.php
Add:
@extends('layouts.app')
@section('content')
<a href="{{ route('products.create') }}" class="btn btn-primary mb-3">
Add Product
</a>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
<th>Stock</th>
<th width="250">Actions</th>
</tr>
</thead>
<tbody>
@forelse($products as $product)
<tr>
<td>{{ $product->id }}</td>
<td>{{ $product->name }}</td>
<td>${{ $product->price }}</td>
<td>{{ $product->stock }}</td>
<td>
<a href="{{ route('products.show', $product) }}" class="btn btn-info btn-sm">
View
</a>
<a href="{{ route('products.edit', $product) }}" class="btn btn-warning btn-sm">
Edit
</a>
<form action="{{ route('products.destroy', $product) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger btn-sm"
onclick="return confirm('Delete this product?')">
Delete
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center">No products found.</td>
</tr>
@endforelse
</tbody>
</table>
{{ $products->links() }}
@endsection
10. Create Product Page
Create:
resources/views/products/create.blade.php
Add:
@extends('layouts.app')
@section('content')
<form action="{{ route('products.store') }}" method="POST">
@csrf
@include('products.form')
<button type="submit" class="btn btn-success">Save</button>
<a href="{{ route('products.index') }}" class="btn btn-secondary">Back</a>
</form>
@endsection
11. Edit Product Page
Create:
resources/views/products/edit.blade.php
Add:
@extends('layouts.app')
@section('content')
<form action="{{ route('products.update', $product) }}" method="POST">
@csrf
@method('PUT')
@include('products.form')
<button type="submit" class="btn btn-success">Update</button>
<a href="{{ route('products.index') }}" class="btn btn-secondary">Back</a>
</form>
@endsection
12. Product Form Partial
Create:
resources/views/products/form.blade.php
Add:
<div class="mb-3">
<label class="form-label">Name</label>
<input
type="text"
name="name"
class="form-control"
value="{{ old('name', $product->name ?? '') }}"
required
>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea
name="description"
class="form-control"
rows="4"
>{{ old('description', $product->description ?? '') }}</textarea>
</div>
<div class="mb-3">
<label class="form-label">Price</label>
<input
type="number"
name="price"
step="0.01"
class="form-control"
value="{{ old('price', $product->price ?? '') }}"
required
>
</div>
<div class="mb-3">
<label class="form-label">Stock</label>
<input
type="number"
name="stock"
class="form-control"
value="{{ old('stock', $product->stock ?? '') }}"
required
>
</div>
13. Show Product Page
Create:
resources/views/products/show.blade.php
Add:
@extends('layouts.app')
@section('content')
<div class="card">
<div class="card-body">
<h4>{{ $product->name }}</h4>
<p><strong>Description:</strong> {{ $product->description }}</p>
<p><strong>Price:</strong> ${{ $product->price }}</p>
<p><strong>Stock:</strong> {{ $product->stock }}</p>
<a href="{{ route('products.edit', $product) }}" class="btn btn-warning">
Edit
</a>
<a href="{{ route('products.index') }}" class="btn btn-secondary">
Back
</a>
</div>
</div>
@endsection
14. Run Laravel App
php artisan serve
Visit:
http://127.0.0.1:8000/products
You now have a complete Laravel CRUD for products.
