Backend DevelopmentFrontend Development

Laravel Inertia Vue 3 Handling Image Uploads With Preview

Laravel Inertia Vue 3 Handling Image Uploads With Preview

In this article we will describe how to handle image upload in Laravel inertia and Vue 3 and we will show a preview of the image when upload success.

 

 

 

Inertia is a laravel package that allows to create client-side apps SPA without creating API’s, instead of returning json responses Inertia works by returning an inertia response from controller actions. Inertia can work alongside Vue2, Vue3 or React.

 

For this example i suppose that you already have a latest version of laravel project. Let’s install inertia dependencies:

server side dependencies:

composer require inertiajs/inertia-laravel

Next create the root template:

resources/views/app.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Inertia Upload Demo</title>

    <!-- Fonts -->
    <link rel="preconnect" href="https://fonts.bunny.net">
    <link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />
    @vite('resources/js/app.js')
    @inertiaHead
</head>
<body>
<div>
    @inertia
</div>
</body>
</html>

The root template is where your inertia app will be loaded. I included the @vite directive to load the compiled javascript and also the @inertiaHead and @inertia directives.

Next setup the Inertia middleware:

php artisan inertia:middleware

This will create the HandleInertiaRequestsMiddleware in app/Http/Middleware directory.

Register this middleware to bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
        $middleware->web(append: [
            \App\Http\Middleware\HandleInertiaRequests::class
        ]);
    })

 

client side dependencies:

Install npm dependencies:

npm install

As we will be using Vue 3 along with inertia so install the Vue 3 Inertia adapter:

npm install @inertiajs/vue3

Also install the vite vue plugin:

npm install @vitejs/plugin-vue

Update vite.config.js by including the vue plugin:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from "@vitejs/plugin-vue";

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
        vue()
    ],
});

 

After installing add this code to resources/js/app.js

import './bootstrap';
import {createApp, h} from "vue";
import { createInertiaApp } from '@inertiajs/vue3'

createInertiaApp({
    resolve: name => {
        const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
        return pages[`./Pages/${name}.vue`]
    },
    setup({ el, App, props, plugin }) {
        createApp({ render: () => h(App, props) })
            .use(plugin)
            .mount(el)
    },
})

The createInertiaApp() function boots the inertia app. The resolve() callback return the appropriate page according to the server response, while the setup() callback mounts and triggers the client-side app. 

Compile the assets with:

npm run dev

Next create a new directory Pages/ inside of resources/js/ directory that will contain the app pages.

 

Creating Upload Controller

Create a new controller

php artisan make:controller UploadController

Open app/Http/Controllers/UploadController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;

class UploadController extends Controller
{
    public function index()
    {
        return Inertia::render('Upload');
    }

    public function store(Request $request)
    {
       
    }
}

I added a method index() that render an inertia page called “Upload” and the method store() that will process the uploading of the file. Let’s create the upload page as a vue component

resources/js/Pages/Upload.vue

<script setup>

</script>

<template>
    <div>
        Inertia upload demo
    </div>
</template>

<style scoped>

</style>

Update the routes to point to this page:

routes/web.php

Route::get('/', [\App\Http\Controllers\UploadController::class, "index"]);

Route::post('/upload', [\App\Http\Controllers\UploadController::class, "store"]);

You may need to run php artisan optimize so that the routes cleared from cache.

Now launch the project using php artisan serve, you will see the “inertia upload demo” text.

 

Database Migration

Create a db migration to store the uploaded file:

php artisan make:migration create_uploads_table

Then open the migration file located at database/migrations/ directory and update the up() method like so:

public function up(): void
    {
        Schema::create('uploads', function (Blueprint $table) {
            $table->id();
            $table->string('file');
            $table->timestamps();
        });
    }

I added a new field “file” represent the uploaded file name.

Now migrate the database:

php artisan migrate

This will run the migrations and create the db tables.

Create a new model for the uploads table:

php artisan make:model Upload

app/Models/Upload.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Upload extends Model
{
    use HasFactory;

    protected $table = "uploads";

    protected $fillable = ["file"];

    protected $appends = ["file_url"];

    public function getFileUrlAttribute()
    {
        return $this->file_url = $this->file ? url('/uploads/' . $this->file) : "";
    }
}

I have set the $fillable and $table properties. I added a custom attribute file_url that contain the full url of the uploaded file.

 

Upload Form

Let’s go to resources/js/Pages/Upload.vue and update with this code:

<script setup>
    import {ref} from "vue";
    import {router} from '@inertiajs/vue3'

    const file = ref(null);

    // to get the validation errors
    defineProps({ errors: Object });

    const onChange = event => {
        file.value = event.target.files[0];
    }

    const handleSubmit = () => {
        router.post('/upload', {
           file: file.value
        });
    }

</script>

<template>
    <div>
        <h2>Image Upload</h2>

        <div class="errors" v-if="errors && errors.file">
            {{errors.file}}
        </div>

        <div v-if="$page.props.flash.success" class="success">
            {{ $page.props.flash.success }}
        </div>

        <form method="post" @submit.prevent="handleSubmit">
            <div class="field">
                <label><b>Image</b></label>
                <input type="file" name="file" accept="image/*" @input="onChange" />
            </div>

            <img :src="$page.props.extra_data.file_url" v-if="$page.props.extra_data && $page.props.extra_data.file_url" />

            <div>
                <input type="submit" />
            </div>
        </form>
    </div>
</template>

<style scoped>
    * {
        font-family: Arial, sans-serif;
    }

    .field {
        display: flex;
        gap: 5px;
    }

    input[type=file] {
        font-size: 16px;
        color: #14c7d9;
    }

    input[type=submit] {
        margin-top: 34px;
        background: #14c7d9;
        font-size: 18px;
        border-radius: 3px;
        border: 4px solid #14c7d9;
        padding: 4px;
        cursor: pointer;
    }

    input[type=submit]:hover {
        background: #0da4b3;
    }

    .errors {
        background-color: #e37070;
        padding: 11px 11px;
        border-radius: 3px;
        margin-bottom: 27px;
    }

    .success {
        background-color: #70e37a;
        padding: 11px 11px;
        border-radius: 3px;
        margin-bottom: 27px;
    }

    img {
        width: 300px;
        height: 300px;
        margin-top: 12px;
        margin-bottom: 8px;
        border: 1px solid #cbc8c8;
    }
</style>

In this code i have added some basic styling in the <style scoped> tag. Next in the <template> i added the upload form html. The form contains an input file and a submit button.

I created a ref variable file to store the selected file from the input using the onChange() function:

const onChange = event => {
     file.value = event.target.files[0];
}

In case you need to upload multiple files, you have to add the multiple attribute to <input type=”file”> tag like so:

<input type="file" multiple />

When submitting the form the handleSubmit() function triggered which invokes inertia router.post() method to post the data to the server and sent the file as the post body:

router.post('/upload', {
           file: file.value
        });

To display the validation errors, the errors can be obtained from vue defineProps() function like so:

defineProps({ errors: Object });

And then accessed in the template. Likewise i displayed the success message from the page props using $page.props.flash.success.

A preview of the image is displayed on upload success from the page props using $page.props.extra_data.file_url.

At this points let’s add the server side code for handling upload, so open UploadController.php and update as shown:

<?php

namespace App\Http\Controllers;

use App\Models\Upload;
use Illuminate\Http\Request;
use Inertia\Inertia;

class UploadController extends Controller
{
    public function index()
    {
        return Inertia::render('Upload');
    }

    public function store(Request $request)
    {
       $request->validate([
          'file' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:10000'
       ]);

       // upload
        $fileName = time().'.'.$request->file->extension();
        $request->file->move(public_path('uploads'), $fileName);

        $upload = Upload::create([
            'file' => $fileName
        ]);

        return redirect()->back()
            ->with('success', 'Image uploaded successfully')
            ->with('extra_data', $upload);
    }
}

In the store() method first i made a simple validation on the file to make sure it’s required and an image file. Then i created a dummy name of the file before storing it in the database and using laravel $request->file->move() method to do the uploading.

To insert the file i used the Upload eloquent model create() function passing in the file attribute. We then redirect back along with success message and an extra data that contains the inserted object.

Update the HandleInertiaRequests.php share() method like so:

public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'flash' => [
                'success' => fn () => $request->session()->get('success')
            ],
            'extra_data' => fn() => $request->session()->get('extra_data')
        ]);
    }

The share() method shares global data to the client side app like flash messages so that we can capture it in the Vue page as described above like:

$page.props.extra_data.file_url

 

 

0 0 votes
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments