Single Image Upload Example using VueJS & Laravel

In this blog post let’s go through the steps involved in building a form in Laravel using VueJS that also has a file input field wherein user can attach an image.

This post assumes that you already have an Laravel project setup on your local.

# Setup VueJS

If your project doesn’t have VueJS setup, Follow along the steps in this tutorial to get the install the Vue library in the Laravel project Laravel 7 Installation with Vue JS

# Prepare Model, Controller and Migration File

You can skip this step, if are just interested in the Vue code that handles the file upload.

Livewire Component Library

For the demonstration, let’s work with an example we will create Posts.

Host Laravel Application on DigitalOcean

Use coupon 5balloons on this Cloudways Affiliate URL to get special discount.

Run the following artisan command to generate a model along with Controller and a Migration file.

php artisan make:model Post -mr

Modify the migration file named create_posts_table to include three new fields named title, description and picture

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->text('description');
        $table->string('picture');
        $table->timestamps();
    });
}

Make sure your project is connected to a database, and run the following command to create the tables into your database.

php artisan migrate

Add the following route entry into the web.php file

Route::resource('posts', 'PostController');

Let’s modify the code of create method of the PostController to return the view file.

/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
    return view('posts.create');
}

# Setting up Vue Component

Let’s dive into the details of the Vue Component. Create a new file named PostCreate.vue inside resources / js / components.

Template

For the simplicity of the demonstration we will be using inline template. Thus I am keeping the template tags inside the component blank

<template>
</template>

Data properties

export default {
    name: 'post-create',
    data(){
        return{
            formFields: {
                title: null,
                description: null,
                picture: null
            },
        }
    },
    ...

Our form is supposed to have three fields named title, description and a picture we have defined an object named formFields that contains the data property for these three fields.

# Form Submission

<post-create inline-template>
    <div>
    <form @submit.prevent="submitForm">
        <div class="form-group">
            <label for="title">Post Title</label>
            <input type="text" name="title" class="form-control" id="title" placeholder="Enter Post Title" v-model="formData.title">
          </div>

          <div class="form-group">
            <label for="description">Post Description</label>
            <textarea name="description" class="form-control" v-model="formData.description"></textarea>
          </div>

          <div class="form-group">
            <label for="description">Picture</label>
            <input type="file" name="picture" class="form-control-file" id="picture" @change="onFileChange">
          </div>

          <div class="form-group">
            <input type="submit" class="btn btn-success" />
          </div>
    </form>
    </div>
</post-create>

We have used the inline-template for our component. We have used the v-bind directive of VueJS to bind different property of the instance to the form.

Notice that we have used two event listeners attached to the form. One is on the change of the file input on which we call the onFileChange method of the instance and another on the form submit event on which we call the submitForm method of the instance.

onFileChange method

onFileChange(event){
    this.formFields.picture = event.target.files[0];
}

Once user selects the image in the file input it triggers the onFileChange method and this is where we get the file from the target input and assign it to the data property named picture.

submitForm(){
    let formData = new FormData();

    formData.append("picture", this.formFields.picture);
    formData.append("title", this.formFields.title);
    formData.append("description", this.formFields.description);

    axios.post('/posts', formData1)
            .then((res) => {
                console.log(res);
            })
            .catch((error) => {
                console.log(error);
    });
},

Notice that instead of directly sending the formFields object to the axios, we are using a Javascript object named FormData. We append all the fields inside formData and then send the formData object to the axios.

The FormData interface provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to “multipart/form-data”.

On the backend (i.e. Laravel ) let’s modify the store method of the PostController to handle the request sent from the frontend.

    public function store(Request $request)
    {
        $validatedData = $request->validate([
            'title' => 'required',
            'description' => 'required',
            'picture' => 'required'
        ]);

        $request->file('picture')->store('pictures');
        Post::create($validatedData);
        return ['message' => 'Post Created'];     
    }

# Preview Image

How about showing a little thumbnail preview to the user on the webpage of the image he has selected for uploading. We can achieve this by using the FileReader javascript API.

Introduce two new data properties to the Vue Component

imagePreview: null,
showPreview: false,

imagePreview will be used to store the actual image and showPreview will be used to determine weather to show the image preview.

Let’s modify the onFileChange method to invoke a new FileReader object and also attach a load event to the reader so that it knows when to show the image.

onFileChange(event){
    /*
    Set the local file variable to what the user has selected.
    */
    this.formData.picture = event.target.files[0];

    /*
    Initialize a File Reader object
    */
    let reader  = new FileReader();

    /*
    Add an event listener to the reader that when the file
    has been loaded, we flag the show preview as true and set the
    image to be what was read from the reader.
    */
    reader.addEventListener("load", function () {
        this.showPreview = true;
        this.imagePreview = reader.result;
    }.bind(this), false);

    /*
    Check to see if the file is not empty.
    */
    if( this.formData.picture ){
        /*
            Ensure the file is an image file.
        */
        if ( /\.(jpe?g|png|gif)$/i.test( this.formData.picture.name ) ) {

            console.log("here");
            /*
            Fire the readAsDataURL method which will read the file in and
            upon completion fire a 'load' event which we will listen to and
            display the image in the preview.
            */
            reader.readAsDataURL( this.formData.picture );
        }
    }
}

Add the following html just after the file input in your form.

<img v-bind:src="imagePreview" width="100" height="100" v-show="showPreview"/> 

We have used the v-bind directive to change the image source just after user selects the file.

That’s all about showing the image preview and uploading file to the form in VueJS.

Demo

single image upload laravel and vuejs

Site Footer