HomeLaravelLaravel 12 CRUD with Image Upload

Laravel 12 CRUD with Image Upload

Laravel 12 CRUD with Image Upload

In this article, you will know how to make Laravel 12 CRUD with image upload. First you need to follow Laravel 12 CRUD Application Example Tutorial. Then you can follow this tutorial.

Step 1: Update the Database with a New Migration

To develop Laravel 12 image upload CRUD application you need to add image_url column in the product table in the MySQL database to hold the image file path which will used to show the image in the blade views.

Generate Migration

php artisan make:migration add_image_url_to_products_table --table=products

Edit Migration File

database/migrations/2025_02_22_xxxxxx_add_image_url_to_products_table.php)

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::table('products', function (Blueprint $table) {
            $table->string('image_url')->nullable()->after('price');
        });
    }

    public function down()
    {
        Schema::table('products', function (Blueprint $table) {
            $table->dropColumn('image_url');
        });
    }
};

Run Migration

php artisan migrate

When you migrate you will have empty column image_url in every row and you find your all previously data unchanged and still there! Isn’t it great?

Step 2: Update the Product Model

Edit app/Models/Product.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $fillable = ['name', 'description', 'price', 'image_url'];
}

Step 3: Update the Controller

Modify app/Http/Controllers/ProductController.php

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    public function index()
    {
        $products = Product::all();
        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',
            'price' => 'required|numeric|min:0',
            'description' => 'nullable|string',
            'image' => 'nullable|image|max:2048',
        ]);

        $data = $request->all();

        if ($request->hasFile('image')) {
            $image = $request->file('image');
            $imageName = time() . '.' . $image->getClientOriginalExtension();
            $image->move(public_path('assets'), $imageName);
            $data['image_url'] = 'assets/' . $imageName;
        }

        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(Request $request, Product $product)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'price' => 'required|numeric|min:0',
            'description' => 'nullable|string',
            'image' => 'nullable|image|max:2048',
        ]);

        $data = $request->all();

        if ($request->hasFile('image')) {
            if ($product->image_url && file_exists(public_path($product->image_url))) {
                unlink(public_path($product->image_url));
            }
            $image = $request->file('image');
            $imageName = time() . '.' . $image->getClientOriginalExtension();
            $image->move(public_path('assets'), $imageName);
            $data['image_url'] = 'assets/' . $imageName;
        }

        $product->update($data);
        return redirect()->route('products.index')->with('success', 'Product updated successfully.');
    }

    public function destroy(Product $product)
    {
        if ($product->image_url && file_exists(public_path($product->image_url))) {
            unlink(public_path($product->image_url));
        }
        $product->delete();
        return redirect()->route('products.index')->with('success', 'Product deleted successfully.');
    }
}

Step 4: Create public/assets and Add Placeholder

  • Create the public/assets directory if it doesn’t exist.
  • Add product_default.jpg (a placeholder image) to public/assets/.

Step 5: Update Blade Views

resources/views/products/index.blade.php (50% Image Size)

@extends('layouts.app')

@section('content')
    <h1>Products</h1>
    @if (session('success'))
        <div class="alert-success">{{ session('success') }}</div>
    @endif
    <a href="{{ route('products.create') }}" class="btn">Add Product</a>
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Price</th>
                <th>Image</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            @foreach ($products as $product)
                <tr>
                    <td>{{ $product->id }}</td>
                    <td>{{ $product->name }}</td>
                    <td>{{ $product->price }}</td>
                    <td>
                        <img src="{{ $product->image_url ? asset($product->image_url) : asset('assets/product_default.jpg') }}" 
                             alt="{{ $product->name }}" 
                             style="width: 50%; height: 50%;">
                    </td>
                    <td>
                        <a href="{{ route('products.show', $product) }}" class="btn">View</a>
                        <a href="{{ route('products.edit', $product) }}" class="btn">Edit</a>
                        <form action="{{ route('products.destroy', $product) }}" method="POST" style="display:inline;">
                            @csrf
                            @method('DELETE')
                            <button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure?')">Delete</button>
                        </form>
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>
@endsection

resources/views/products/create.blade.php (Full-Size Preview)

@extends('layouts.app')

@section('content')
    <h1>Create Product</h1>
    <form action="{{ route('products.store') }}" method="POST" enctype="multipart/form-data">
        @csrf
        <div class="form-group">
            <label>Name</label>
            <input type="text" name="name" class="form-control" required>
        </div>
        <div class="form-group">
            <label>Description</label>
            <textarea name="description" class="form-control"></textarea>
        </div>
        <div class="form-group">
            <label>Price</label>
            <input type="number" name="price" class="form-control" step="0.01" required>
        </div>
        <div class="form-group">
            <label>Image</label>
            <input type="file" name="image" class="form-control" accept="image/*" onchange="previewImage(event)">
            <img id="image-preview" src="{{ asset('assets/product_default.jpg') }}" alt="Preview" style="display: block; margin-top: 10px;">
        </div>
        <button type="submit" class="btn">Save</button>
        <a href="{{ route('products.index') }}" class="btn btn-secondary">Cancel</a>
    </form>

    <script>
        function previewImage(event) {
            const preview = document.getElementById('image-preview');
            preview.src = event.target.files[0] ? URL.createObjectURL(event.target.files[0]) : "{{ asset('assets/product_default.jpg') }}";
        }
    </script>
@endsection

resources/views/products/edit.blade.php (Full-Size Preview)

@extends('layouts.app')

@section('content')
    <h1>Edit Product</h1>
    <form action="{{ route('products.update', $product) }}" method="POST" enctype="multipart/form-data">
        @csrf
        @method('PUT')
        <div class="form-group">
            <label>Name</label>
            <input type="text" name="name" class="form-control" value="{{ $product->name }}" required>
        </div>
        <div class="form-group">
            <label>Description</label>
            <textarea name="description" class="form-control">{{ $product->description }}</textarea>
        </div>
        <div class="form-group">
            <label>Price</label>
            <input type="number" name="price" class="form-control" step="0.01" value="{{ $product->price }}" required>
        </div>
        <div class="form-group">
            <label>Image</label>
            <input type="file" name="image" class="form-control" accept="image/*" onchange="previewImage(event)">
            <p>Current Image:</p>
            <img id="image-preview" src="{{ $product->image_url ? asset($product->image_url) : asset('assets/product_default.jpg') }}" 
                 alt="Product Image" style="margin-top: 10px;">
        </div>
        <button type="submit" class="btn">Update</button>
        <a href="{{ route('products.index') }}" class="btn btn-secondary">Cancel</a>
    </form>

    <script>
        function previewImage(event) {
            const preview = document.getElementById('image-preview');
            preview.src = event.target.files[0] ? URL.createObjectURL(event.target.files[0]) : "{{ $product->image_url ? asset($product->image_url) : asset('assets/product_default.jpg') }}";
        }
    </script>
@endsection

resources/views/products/show.blade.php (Full-Size Image)

@extends('layouts.app')

@section('content')
    <h1>Product Details</h1>
    <p><strong>Name:</strong> {{ $product->name }}</p>
    <p><strong>Description:</strong> {{ $product->description ?? 'N/A' }}</p>
    <p><strong>Price:</strong> ${{ $product->price }}</p>
    <p><strong>Image:</strong></p>
    <img src="{{ $product->image_url ? asset($product->image_url) : asset('assets/product_default.jpg') }}" 
         alt="{{ $product->name }}">
    <a href="{{ route('products.index') }}" class="btn">Back</a>
@endsection

Step 6: Test the Application

Run the Server

php artisan serve

Verify:

  • List View: Images or product_default.jpg at 20% size.
  • Create: Full-size preview with product_default.jpg initially.Laravel CRUD with image upload
  • Edit: Full-size current image or product_default.jpg, with preview on new upload.
  • laravel 12 CRUD application with image upload
  • Show: Full-size image or product_default.jpg.

Hope, this tutorial help you have a Laravel image upload CRUD application.

Share: 

No comments yet! You be the first to comment.
Categories