Laravel

Building a Complete CRUD Application in Laravel 13: A Step-by-Step Guide (Updated for Laravel 11/12/13)

Building a Complete CRUD Application in Laravel 13: A Step-by-Step Guide (Updated for Laravel 11/12/13)

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-app

Start 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 migrate

Step 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 -mcr

The `-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 migrate

Step 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 UpdateProductRequest

In `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 @foreach($products as $product) @endforeach
ID Image Name Price Actions
{{ $product->id }} @if($product->image) {{ $product->name }} @endif {{ $product->name }} ${{ number_format($product->price, 2) }} View Edit
@csrf @method('DELETE')
{{ $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

@csrf
@error('name') {{ $message }} @enderror
@endsection

For `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.

 

Written by

Hupen Pun

Dedicated to sharing valuable insights, tech tutorials, and educational resources to help you level up your skills.