DEV Community

Cover image for Create a Image Upload component for Laravel breeze inertia and vue 3
vimuth
vimuth

Posted on

Create a Image Upload component for Laravel breeze inertia and vue 3

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) {
            //
        });
    }
};
Enter fullscreen mode Exit fullscreen mode

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

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>
Enter fullscreen mode Exit fullscreen mode

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 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.']);
}
Enter fullscreen mode Exit fullscreen mode

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
        ];
    }
Enter fullscreen mode Exit fullscreen mode

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');
});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)