You are currently viewing Livewire 3 Image CRUD (Image Upload) Tutorial

Livewire 3 Image CRUD (Image Upload) Tutorial

In this Livewire 3 Image CRUD Tutorial or Livewire 3 Image Upload Tutorial you will learn, How to create Livewire 3 Image CRUD system(Livewire 3Image Upload system) . We will see How to Upload an image to the database, How to edit the Image & replace it with new Image from database & storage folder using Livewire 3 . At last we will see How to delete the Image from Both the database & storage folder using Livewire 3. We will create a fully Functional Livewire 3 Image Upload app/system or in other words Livewire 3 Image CRUD app/system.

Before we start I assume that You have already installed Laravel (Laravel 10 or later) & Livewire 3. If not then check the below tutorial & then come back to this tutorial.

How To install Livewire v3 in Windows – PhpNodeTuts

I have already installed Laravel & Livewire and included Tailwind css cdn in the app layout.

CREATING DATABASE

First we will create a database for our Project. Open phpMyAdmin & create a database. Here I will create image_crud database. After creating the database, open .env file & enter the database name inside it.

CREATING POST MODEL

Go to your project directory, open terminal & enter below Command to create Post model & posts migration.

php artisan make:model Post -m

This command will create two files : a Post model in the Models directory & posts migration. Here we are using -m flag which will automatically generate create_posts_table migration file.

Now we need to add fields in our migration file. Open create_posts_table migration file, which is inside database->migrations folder. Now we will add two fields into our create_posts_table migration file, title & image . The datatype of title will be string & the datatype of image will also be string. Enter below lines after $table->id();

  $table->string('title');
  $table->string('image');

Now we need to migrate the database, so in the terminal enter the following command to migrate the database.

php artisan migrate

CREATING POST COMPONENT

Now we will create a Livewire component called ImageUpload . Open your terminal & type below command to create the ImageUpload Livewire component.

php artisan make:livewire ImageUpload

The above command will generate two files. ImageUpload  component inside the Livewire folder & image-upload file inside the resources/views/livewire folder.

ADDING ROUTE

Before we proceed further, lets add Route for our Image CRUD. Open web.php file and add below code inside it.

Route::get('/image-upload', ImageUpload::class)->name('posts');

Enter the below code in web.php file after <?php tag

CREATE METHOD

Now Open ImageUpload.php file and add below code inside ImageUpload Class

public $isOpen = 0;
 
    public function create()
    {
        $this->openModal();
    }
    public function openModal()
    {
        $this->isOpen = true;
    }
    public function closeModal()
    {
        $this->isOpen = false;
    }

In the above code, we have defined public $isOpen = 0, It means that by default the modal will be closed.

LIVEWIRE 3 CRUD TUTORIAL STEP BY STEP(UPDATED)

public function create(): This method is likely called when you want to open the modal window to create a new item or perform some action. It typically triggers the openModal() method to set the isOpen property to true, indicating that the modal should be shown on the image-upload.blade.php  view.

public function openModal(): This method sets the isOpen property to true, indicating that the modal is open or visible. When the isOpen property is true, the Livewire component will render the modal in the post.component.blade view.

public function closeModal(): This method is responsible for closing the modal window. It sets the isOpen property to false, indicating that the modal should be hidden or closed. When the isOpen property is false, the Livewire component will not render the modal in the image-upload.blade.php  view.

Now, open the image-upload.blade.php  view file and enter the below code inside the <div></div> tag

<div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
            <div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
                <div class="max-w-xl">
                <section>
                    <div class="my-4">
                        <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" wire:click="create">Add Image</button>
                    </div>
                    @if($isOpen)
                    <div class="fixed inset-0 flex items-center justify-center z-50">
                        <div class="absolute inset-0 bg-black opacity-50"></div>
                        <div class="relative bg-gray-200 p-8 rounded shadow-lg w-1/2">
                            <!-- Modal content goes here -->
                            <svg wire:click.prevent="$set('isOpen', false)"
                            class="ml-auto w-6 h-6 text-gray-900 dark:text-gray-900 cursor-pointer fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18">
                           <path d="M14.53 4.53l-1.06-1.06L9 7.94 4.53 3.47 3.47 4.53 7.94 9l-4.47 4.47 1.06 1.06L9 10.06l4.47 4.47 1.06-1.06L10.06 9z" />
                       </svg>
                            <h2 class="text-2xl font-bold mb-4">Create Post</h2>
                            <form>
                                <div class="mb-4">
                                    <label for="title" class="block text-gray-700 font-bold mb-2">Title:</label>
                                    <input type="text" id="title" class="w-full border border-gray-300 px-4 py-2 rounded">
                                </div>
  <div class="mb-4">
  <label for="image" class="block text-gray-700 font-bold mb-2">Image:</label>
  <input type="file" id="title" class="w-full border border-gray-300 px-4 py-2 rounded">
  </div>                               
                                <div class="flex justify-end">
                                    <button type="submit" class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded mr-2">Create </button>
                                    <button type="button" class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded" wire:click="closeModal">Cancel</button>
                                </div>
                            </form>
                        </div>
                    </div>
                    @endif
                </section>
                </div>
            </div>
        </div>
    </div>


In the above code we have added a button to add a Post. We have added a click event wire:click=”create” on the button, which will open the modal. We have added closeModal method on the cancel button, which will used to close the modal.

Now open Post model & add below code after use HasFactory;

  protected $fillable = ['title','image'];

ADDING VALIDATION RULES

Now open ImageUpload.php file and enter the below code before the create method.

use WithFileUploads;
#[Rule('image|max:2048')] // 2MB Max
    public $image;
 
    #[Rule('required|min:3')]
    public $title;

Here we have $title & $image are two variables which are publicly available.

#[Rule] is a new feature in Livewire v3, used for validation.

The code #[Rule(‘image|max:2048’)], means that only image can be uploaded & the max size of the file should be 2MB.

The #[Rule(‘required|min:3’)],  means that the field is required & minimum 3 charaters are required for this field.

To use this we need to import the below code above.

use Livewire\Attributes\Rule;

Here we have used WithFileUploads, which is a trait used for Uploading files in Livewire.

Import it using the following code.

use Livewire\WithFileUploads;

STORING IMAGE & DATA

Now, to store the image &  data into the database, we will add store() method after the create method.

    public function store()
    {
        $this->validate();
        Post::create([
            'title' => $this->title,
            'image' => $this->image->store('public/photos')
        ]);
        session()->flash('success', 'Image uploaded successfully.');
        $this->reset('title','image');
        $this->closeModal();
    }

Make sure to import the Post model, before the class.

  use App\Models\Post;

In the above store() method,  $this->validate() will validate the data,

The Post::create() method will create a new instance of the Post model and saves it to the database.

The below code will store the image into the public/photos directory:

 'image' => $this->image->store('public/photos')
session()->flash(‘success’, ‘Post created successfully.’);

The above code will display a flash message on the image-upload.blade.php file.

$this->closeModal(); This will close the modal.

$this->reset(); This line calls the reset method, which is another built-in Livewire method used to reset the component’s properties to their initial state. In this context, it clears the title and body properties, making the form fields empty after the post is successfully created.

To make these files accessible from the web, you should create a symbolic link from public/storage to storage/app/public.

php artisan storage:link

Now we need to make the Form component work, we need to add wire:model directive & the store() method, so that when the user enter the data in title & chose an image file the data gets inserted into the database, and the modal gets closed.

Open image-upload.blade.php file & enter the below code.

In the title input enter wire:model=”title” and in the image input enter wire:model=”image”.

<input type="text" wire:model="title" id="title" class="w-full border border-gray-300 px-4 py-2 rounded">
<input type="file" wire:model="image" id="title" class="w-full border border-gray-300 px-4 py-2 rounded">

Now enter the below code in form tag.

<form wire:submit="store">

Now open localhost:8000/image-upload in your browser,
Click on Add Button, it will open the Modal.

Now enter title & choose an image file and click on Create button.

Now open phpMyAdmin and check the database, you will see title and image are inserted into the database.

DISPLAYING SUCCESS MESSAGE

Now when the data is inserted into the database, we will display a success message to let the user know that the data has been inserted successfully. To display the success message, open image-upload.blade.php file and enter below code after the section tag.

@if (session()->has('success'))
    <div class="relative flex flex-col sm:flex-row sm:items-center bg-gray-200 dark:bg-green-700 shadow rounded-md py-5 pl-6 pr-8 sm:pr-6 mb-3 mt-3">
        <div class="flex flex-row items-center border-b sm:border-b-0 w-full sm:w-auto pb-4 sm:pb-0">
            <div class="text-green-500" dark:text-gray-500>
                <svg class="w-6 sm:w-5 h-6 sm:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
            </div>
            <div class="text-sm font-medium ml-3">Success!.</div>
        </div>
        <div class="text-sm tracking-wide text-gray-500 dark:text-white mt-4 sm:mt-0 sm:ml-4"> {{ session('success') }}</div>
        <div class="absolute sm:relative sm:top-auto sm:right-auto ml-auto right-4 top-4 text-gray-400 hover:text-gray-800 cursor-pointer">
            <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
        </div>
    </div>
@endif

Now you will see a success message after the data gets inserted into the database.

DISPLAYING INPUT ERROR MESSAGES

To display the error messages, enter the below code after input title :

<span class="text-red-500">@error('title') {{ $message }} @enderror</span>

Enter the below code after image field :

<span class="text-red-500">@error('image') {{ $message }} @enderror</span>

You can see that now if you try to submit the form, without entering data, it will display error messages.

You may notice that when we click on the Cancel button, the validation messages are not cleared.

To fix this open ImageUpload.php file and add below code in the openModal() method.

$this->resetValidation();

LARAVEL LIVEWIRE3 DEPENDENT DROPDOWN TUTORIAL

DISPLAYING DATA INTO A TABLE

Now we will display the inserted data into the table. Add the below code in the render method.

public function render()
        {
            return view('livewire.image-upload',[
            'posts' => Post::all()
        ]);
}

Now in the post-component.blade.php file add the below code after the </section> tag to display post title and body in the table.

   {{-- table starts --}}
            <div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-3">
                <table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
                    <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
                        <tr>
                            <th scope="col" class="px-6 py-3">
                                Title
                            </th>
                            <th scope="col" class="px-6 py-3">
                                Image
                            </th>
                            <th scope="col" class="px-6 py-3">
                                Action
                            </th>
                        </tr>
                    </thead>
                    @forelse ($posts as $post)
                    <tbody wire:key="{{ $post->id }}">
                        <tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700">
                            <th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
                                {{$post->title}}
                            </th>
                            <td class="px-6 py-4 text-gray-900">
                                <img src="{{ Storage::url($post->image)}}" alt="Uploaded Image Preview" style="max-width: 100px;">
                            </td>

                            <td class="px-6 py-4 text-gray-900">
                                <button class="">
                                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="ml-2 mt-0 w-4 h-4">
                                        <path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" />
                                      </svg>
                                </button>
                            <button class="" >
                                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="ml-2 mt-0 w-4 h-4">
                                    <path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
                                  </svg>
								  </button>
                            </td>
                        </tr>
                    </tbody>
                    @empty
                <p>No post found</p>
            @endforelse
                </table>
            </div>
            {{-- table ends --}}

In the above code we have used Laravel forelse loop to loop the data.

We have used the Storage Façade with url method to get the url of the uploaded image in the below code :

Storage::url($post->image)

PAGINATION

Now to add pagination, open ImageUpload.php file & add use WithPagination trait.

Don’t forget to import withPagination trait above.

use Livewire\WithPagination;

In the render method, add the below code.

public function render()
    {
        return view('livewire.image-upload',[
            'posts' => Post::paginate(5),
        ]);
    }

The above code will display 5 records per page.

Now open image-upload.blade.php file and add the below code after the </table> tag

{{ $posts->links() }}

Now enter 10-20 records into the database & you will see pagination links.

Now for updating & deleting post, we need post id.

In the ImageUpload file add the below code for post id. Also add oldImage property.

public $postId;
public $oldImage;

LIVEWIRE 3 CRUD TUTORIAL USING BOOTSTRAP 5

EDITING DATA

Now we will create edit() method, which will be used to display the old data into the title & textarea for update. Add below code after the store() method

  public function edit($id)
    {
        $post = Post::findOrFail($id);
        $this->postId = $id;
        $this->title = $post->title;
        $this->oldImage = $post->image;

        $this->openModal();
    }

Now open image-upload.blade.php file and enter the below code in <h2></h2>tag

<h2 class="text-2xl font-bold mb-4">{{ $postId ? 'Edit Image' : 'Add Image' }}</h2>

In the form tag add below code :

<form wire:submit="{{ $postId ? 'update' : 'store' }}">

In the Create button add the following code.

{{ $postId ? 'Update' : 'Create' }}

Now enter the below code into the edit button.

wire:click="edit({{ $post->id }})"

When the edit button is clicked, the modal will be opened with the data from the database. Now if you click on the Edit button it will open the modal with the data. You can see that the title has also been changed to Edit Image.

UPDATING DATA

Now we need to update the existing data.

In the ImageUpload  file add below code after edit() method.

public function update()
    {  
$this->validate();
	
	 $post = Post::findOrFail($this->postId);
        $photo = $post->image;
            if($this->image)
            {
                Storage::delete($post->image);
                $photo = $this->image->store('public/photos');
            }else{
                $photo = $post->image;
            }

            $post->update([
                'title' => $this->title,
                'image' => $photo,
            ]);
            $this->postId='';

            session()->flash('success', 'Image updated successfully.');
            $this->closeModal();
            $this->reset('title', 'image', 'postId');
    }

Important : When updating the Data, if you only want to validate title field & not the image field. In the above code, in the update() method Instead of $this->validate() use the below code :

 $this->validate([
        'title' => 'required|min:3', // Validate as an image and maximum file size of 2MB
    ]);

This way when updating the data only title is a required field, it can’t be blank and the Image field is not a required field.

It will keep the old image and just change the title of the image file.

Now Add below code before the input file field.

                   @if($oldImage)
                   <h3>Old Image</h3>
                    <img src="{{Storage::url($oldImage)}}" alt="" class="h-20 w-40">
                    @endif
                        @if ($image)
                        <h3>New Image</h3>
                        <img src="{{ $image->temporaryUrl() }}" class="h-20 w-40">
                    @endif

Now click on the edit button, and try to update a record. You can see that the record is updated successfully.

You can see that the old image has been removed first from both the database & photos folder.

The new uploaded image will be inserted.

DELETING DATA

Now we need to delete data.

In the PostComponent, add the below code, after update() method.

public function delete($id)
  {
        $singleImage = Post::findOrFail($id);
        Storage::delete($singleImage->image);
           $singleImage->delete();
        session()->flash('success','Image deleted Successfully!!');
        $this->reset('title','image');
  }

In the above code , we will first find the Image, then delete the image from folder/directory and then delete the data from database.

Now in the image-upload.blade.php file enter the below code in delete button :

<button class="" wire:click="delete({{ $post->id }})">

Now click on the delete icon/button & you can see that the data is deleted.

CONFIRMATION BEFORE DELETING

To show a confirm dialog, you can use sweet alert or show a modal.

Here we will show simple javascript confirm box before deleting the record.

Replace the delete button code with the below code :

  <button type="button" class=""
                      onclick="return confirm('Are you sure you want to delete this item?') || event.stopImmediatePropagation()" wire:click="delete({{ $post->id }})"">
                      <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="ml-2 mt-0 w-4 h-4">
                          <path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
                        </svg>
                      </button>

Now you will see a confirmation box before deleting a Record. Now we have a complete Image CRUD Application.

Video Tutorial

This Post Has 10 Comments

  1. Roms

    So Glad that i found your website, I learned from you. thank you so much!

    1. Admin

      Thanks….keep visiting 🙂

  2. Roms

    Hi Admin,

    Do you figure it out what validation code to use in order to validate the image field only when creation/store only?

    Thanks

    1. Admin

      Sorry, I didn’t get you!

      Do u want to validate only image file?

      You don’t want to validate the title field?

      1. Roms

        i want to validate the image only when creating/store and nullable when editing/update, because in your code the image field is always required even editing.

        1. Admin

          Use the below code instead of $this->validate() in the update() method

          $this->validate([
          ‘title’ => ‘required|min:3’, // Validate as an image and maximum file size of 2MB
          ]);

          This way when updating the data only title is a required field, it can’t be blank and the Image field is not a required field.

          It will keep the old image and just change the title of the image file when updating.

          Hope this helps. 🙂

          1. Roms

            Wow, its works sir, thank you so much

          2. Admin

            You are welcome..keep visiting 🙂

  3. Chris

    Thank you for posting this! I have been trying to find a way to build these features into a project of mine for a while but with every approach I was running into problems . Until discovered this tutorial. It works like a charm, thanks!

    1. Admin

      Thank you! I’m glad the tutorial was helpful.

Leave a Reply