Create a Image Upload component for Laravel breeze inertia and vue 3
Published: 2024-03-16 16:32:13


Laravel Breeze and Inertia.js are tools that simplify the development process for Laravel applications, offering a streamlined way to create modern, interactive web applications. When used together, Laravel Breeze and Inertia.js offer a powerful combination for starting new Laravel projects. Breeze sets up the authentication scaffolding and basic layout, while Inertia.js handles the client-side rendering, allowing for a modern, interactive user experience without the complexity of managing a separate frontend and API. This setup is ideal for developers looking to quickly bootstrap new projects with sensible defaults and the flexibility to expand and customize as needed.

But there's a major issue when it comes to the profile page because there's nowhere to upload an image. In this tutorial we will create an image upload component.

### First lets start by installing Laravel and Breeze
We start with installing Laravel and Breeze. If you have already installed Laravel and Breeze you can skip these steps

`composer create-project --prefer-dist laravel/laravel your_project_name`

Then install laravel breeze

`composer require laravel/breeze --dev`
`php artisan breeze:install vue`
`npm install && npm run dev`
`php artisan migrate`
`php artisan serve`

###Creating file upload component
Now let's start creating file upload component. Let's start by adding migrations to add a new column in database to save 'profile image'

`php artisan make:migration add_profile_image_to_users_table --table=users`

This is the code for migration file.

```
<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->text('profile_image')->nullable()->after('email');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('users', function (Blueprint $table) {
            //
        });
    }
};
```

now run `php artisan migrate`. Above migration **added profile_image column to users table**

Now let's start create the component. In resources\js\Pages\Profile\Partials directory we have components.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ivngyxiu9fs49hpoow7c.png)

Let's add our new component now. The name it as UpdateProfileImageForm.vue 

```
<script setup>
import { useForm } from "@inertiajs/vue3";
import PrimaryButton from "@/Components/PrimaryButton.vue";
import { ref } from "vue";

const form = useForm({
  profile_image: null,
});

const imagePreviewUrl = ref(null);

function handleFileChange(event) {
  form.profile_image = event.target.files[0];
  if (form.profile_image) {
    imagePreviewUrl.value = URL.createObjectURL(form.profile_image);
  }
}

function submit() {
  form.post("/profile_image");
}
</script>

<template>
  <form @submit.prevent="submit">
    <div>
      <img
        v-if="imagePreviewUrl"
        :src="imagePreviewUrl"
        class="max-w-xs max-h-xs mt-4"
        alt="Image preview"
      />
      <input type="file" @change="handleFileChange" />
    </div>
    <div class="flex items-center gap-4 mt-6">
      <PrimaryButton :disabled="form.processing">Save</PrimaryButton>
    </div>
  </form>
</template>
```

Here we have created a reactive form with useForm(), And added the field profile_image with form submit using inertiajs. Apart from this we have added an image show a preview.

`URL.createObjectURL(form.profile_image);`  creates a DOMString containing a URL representing the object given in the parameter. When used with file inputs, as in your example, it generates a temporary URL that points to the file selected by the user. This URL can then be used in the web application to refer to or display the file before it's uploaded to a server. Commonly, it's used as the src attribute of an <img> tag to show a preview of an image file

Now let's add a new function to upload image and route to that function.

inside app\Http\Controllers\ProfileController.php

```
public function uploadImage(ProfileImageRequest $request){
        $user = Auth::user(); // Get the authenticated user

        if ($request->hasFile('profile_image')) {
            
            $file = $request->file('profile_image');
            $filename = "XaYPfty10". $user->id . '.' . $file->getClientOriginalExtension(); // Create a unique filename

            $path = $file->storeAs('public/profile_images', $filename);

            $user->profile_image = $filename;
            $user->save();

            return redirect()->back()->with('message', 'Profile image updated successfully.');
        }

        // Handle the case where no file was uploaded
        return redirect()->back()->withErrors(['profile_image' => 'Please upload an image.']);
}
```

Also I have created a ProfileImageRequest with this inside it,

```
public function rules(): array
    {
        return [
            'profile_image' => ['required', 'image', 'mimes:jpg,jpeg,png,gif', 'max:2048'], // max 2048kb or 2mb
        ];
    }
```

And this is route

```
Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::post('/profile_image', [ProfileController::class, 'uploadImage'])->name('profile.imageUpload');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
```

MAke note that I have added Route::post instead Route::patch since patch method didn't work well with image upload.

And there are few things are remaining we can do to improve this code. One is show the image from database when we load first.

in here resources\js\Pages\Profile\Partials\UpdateProfileImageForm.vue we can add like this.

```
const user = usePage().props.auth.user;

const initialImageUrl = user.profile_image ? `/storage/profile_images/${user.profile_image}` : null;
const imagePreviewUrl = ref(initialImageUrl);
```

This is the completed full code

```
<script setup>
import { useForm, usePage } from "@inertiajs/vue3";
import PrimaryButton from "@/Components/PrimaryButton.vue";
import { ref } from "vue";

const user = usePage().props.auth.user;

const form = useForm({
  profile_image: null,
});

const initialImageUrl = user.profile_image ? `/storage/profile_images/${user.profile_image}` : null;
const imagePreviewUrl = ref(initialImageUrl);

function handleFileChange(event) {
  form.profile_image = event.target.files[0];
  if (form.profile_image) {
    imagePreviewUrl.value = URL.createObjectURL(form.profile_image);
  }
}

function submit() {
  form.post("/profile_image");
}
</script>

<template>
  <form @submit.prevent="submit">
    <div>
      <img
        v-if="imagePreviewUrl"
        :src="imagePreviewUrl"
        class="max-w-xs max-h-xs mt-4"
        alt="Image preview"
      />
      <input type="file" @change="handleFileChange" />
    </div>
    <div class="flex items-center gap-4 mt-6">
      <PrimaryButton :disabled="form.processing">Save</PrimaryButton>
    </div>
  </form>
</template>
```

And since we have stored images inside storage we have to create the symlinks.

> php artisan storage:link

If any one wonders what are the symlinks they are the shortcuts in linux. If you use windows you can see them like this,


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4s0wj0ey0bceuv8q1btw.png)