PHP

Image Uploads Using Laravel 5.4 With VueJS

It’s been a little while since I made a ‘proper’ tutorial so I have decided to put this one together in the hopes that it helps those starting out with Laravel and Vue. I assume you have basic knowledge of HTML & CSS, plus I assume you have at least had a quick perusal of the Laravel Documentation and Vue documentation. If you haven’t you may find some of the concepts here a little foreign.

Starting Notes

I’m unable to provide a demo for this project however it will cover how to create a basic image upload system using DropzoneJS, Vue and Laravel. There are also a couple of prerequisites.

Laravel and Vue are best used with NPM so you will need to have Node installed. As of writing version 6.10.2 LTS is the version I am currently using and Laravel’s npm install worked fine. Knowledge of NPM would be helpful, but I will be going over all commands so it is not essential.

You will also need Composer installed in your development environment. If you are using Docker, make sure it is installed in your container. If you are using a VM make sure it is installed on your VM. If you are using a PHP install you will need to install it on your PC.

A final note is that I am using Docker (Laradock). You should be able to use Docker, Vagrant or even just a standard webserver like XAMPP, but I have not used the latter and so cannot confirm it does actually work.

Setting Up Laravel

It would take too long to cover all of the various types of install such as Docker, VM, etc so I will just be doing a quick recap of the install instructions from the Laravel docs. I prefer the Composer install method so we will follow that one.

Open up a terminal (I use cmder for Windows) and navigate to the folder you wish to install Laravel in and run:

This will install into the directory you are currently in. If you wish to create a directory replace . with the name of the directory you wish to create.

Remember that your web server needs to be set to serve the public folder inside the laravel install. Instructions for doing that depend on your setup but for Apache this VHosts tutorial on Digital Ocean might help, and for Nginx this Server Blocks tutorial on Digital Ocean might help.

After the install has completed we need to install all of the Node packages. This will allow us to use webpack.

Once complete we can now use webpack. To keep things simple and concentrate on the upload system we are just going to use the files given to us by Laravel for it’s welcome page.

Creating a Controller

First let’s create our controller. We’ll use Artisan to create the controller.

I encourage you to name your future controllers better than I have here, but for the purposes of this tutorial that will be fine. While we are using Artisan let’s make sure to make our public storage folder accessible via a URL. To do that run:

This will make your storage/public folder accessible via http://yoursite.com/storage.

Open up your controller (/app/http/controllers/MainController.php) and let’s get started. We are going to write out all of our methods here and each one will translate to a route. They will be as follows.

  1. Home – The entry point and home page. Returns HTML.
  2. Upload – The upload route, responds to POST requests only. Returns JSON Response.
  3. List – Image listing route. Simple directory listing. Returns JSON Response.

So, let’s write these out.

Be careful not to miss that I have included the Storage Namespace at the top of the file since we are using the Storage Facade to help store our image in the file system.

Some fairly simple code here, but let’s go through everything.

Home Method

Simply returns the welcome blade template. Currently it just shows the Laravel logo, but we’ll edit that soon.

Upload Method

Takes the injected Request object and checks for a file and if that file is valid. Also stores the file using Laravel’s file system Facade. Returns a error via JSON if not valid or no file exists. Stores and returns a JSON object if the file is stored successfully.

List Method

Since I am not using a DB in this tutorial, to help keep things simple, I am just listing the files found in the directory. This would probably be best replaced with something that lists the files from a data store like a Database rather than a file system like this. To be on the safe side pre-make your images folder inside /storage/app/public.

Now that we have our methods, let’s setup the routes. We do that in the routes file at /routes/web.php.

Now that the routes have been created we can start to work on our Vue code.

Vue Components – Image List Component

Laravel automatically installs Vue when you do your initial npm install, so let’s take advantage of that. Before we start we need to pull in Dropzone for our uploader to use.

We are going to be using the excellent Vue Dropzone by rowanwins as it bundles Dropzone as a ready made vue component.

Now let’s get webpack running.

It will take a little while the first time, but should be quicker after that initial run. This command will watch for file changes and then recompile when it detects them. There is also:

If you prefer not to use file watching.

Now you should be able to see that Laravel gives us an example component in the /resources/assets/js/components folder. Let’s use that. Rename the file to ImageList.vue and place the following code in there.

Let’s explain this a little.

First up is our template. It uses Bootstrap classes since it is pre-included with Laravel. The only new part here is the v-for attribute and the :src attribute. v-for works exactly like a for... loop in JavaScript. Here it repeats the tag but it would also repeat any children if the tag were a div for example. The :src attribute allows us to use a variable as the value for the tag’s src attribute. Somewhat like src="{{ file }}".

Now for the Vue code. First up we import Axios. Axios is a simple promise based HTTP client that is great for AJAX requests if you don’t need jQuery. jQuery is included with Laravel, but to make things interesting I thought we’d use Axios here instead.

On mount of the component we run a method called refresh(). We create a url prop. The url prop will be used to set the url to retrieve the list of files. This enables us to change the route at a later date without needing to edit the component. We create a files data variable to store our list of files. Finally we create the refresh method. It makes a simple get request to the URL set on the url prop using Axios and then sets the files variable to the value of the response. Note that the response data is held in response.data.

In out /resources/assets/js/app.js file we will need to inform Vue of our component. To do that replace the example component with this one:

Vue Components – Upload Component

Now we will create the upload component.

Create a file called Upload.vue in your components folder and then add the component to our app.js file.

Now let’s write out our upload component. This one will be using the Vue Dropbox component.

Here we have some simple HTML again but this time we have a custom tag called dropzone this is a the Vue Dropzone component. We give it an ID, pass in the upload path, pass a CSRF token and define a method to run on a successful upload. These extra attributes are props defined by the Vue Dropzone component and allow us to customise things easily. The last one is not a prop and is actually an event triggered by Vue Dropzone that we can respond to.

In the JavaScript we include the Vue Dropzone component. We create a csrf prop so we can pass in our csrf token, an action prop so we can set the upload url from the parent component, and we create the showSuccess method to fire when the Dropzone triggers the vdropzone-success event. For the moment it just logs something to the console but we’ll do something better with it soon.

Creating The Home View

Okay, we are nearly done. Now we’ll create the home view. Open up /resources/views/welcome.blade.php replace the code in there with this:

Here we have added an ID to our main container. This matches the ID used when Vue is initialised in our app.js file. We’ve also added in our upload and image-list components giving them the csrf, action and url prop values.

Here is the CSS if you want to style it the same way. Place this in /resources/sass/app.scss. Replace any code that is already there.

It is just simple styling using the Laravel welcome screen styles.

With that you should be ready to go with a basic example. If you visit your Laravel application you should see a Dropzone and you should be able to drop an image or click and upload. You may have noticed though that you’ll only see your list of images if you refresh… That’s rubbish. Let’s fix that.

Vue Events – The Bus

In Vue you can only listen to and notify of parent/child component events, however you can get around this by using an event bus. Instead of the parent/child emitting and responding to events the bus emits and responds inside the component’s code.

Why is this relevant? Well we need to re-request data on our image-list component when the Dropzone component has finished a successful upload. Since the image-list component is not related to the Upload or Dropzone component we have to use an event bus. Let’s make one.

Create a new file called bus.js next to app.js. Enter the following:

Writing our bus as a file like this allows us to import it as a module which allows us to use the same event bus in multiple components.

Now in our ImageList.vue file we need to modify our code. Here is the whole code with the newly added lines highlighted.

We have imported our bus and added some code here that will be ran when the created hook is ran by Vue. This will be triggered when the bus is sent the uploaded event. If the file it receives was uploaded successfully we trigger a refresh of the file list. Simple.

Now in our Upload.vue component.

This one is even simpler. We import our bus again and use the $emit method given to us by our event bus to trigger the uploaded event when Dropzone fires it’s vdropzone-success event. We pass the file data along with it too.

Mission Complete!

That’s it, we are finally done. You should now have a working uploader using Laravel, Vue and DropzoneJS.

FAQ

Here is a list of questions that you may have, if you have a question I haven’t answered here please let me know via the comments.

Why do we need a second CSRF token?

Laravel includes the CSRF token into Axios’ headers. You just need to add:

To your main blade file, as we did in this tutorial. In fact if you didn’t add this your requests to the /list route would be refused due to a token mismatch. However Vue Dropzone makes separate requests to upload the files and so we need to pass the CSRF token to it separately add send it with it’s headers.

Why didn’t you do it this way?

As with everything web development based, I’m sure there is. This is just my way of doing it. Let me know in the comments if you have improvements, or tips, I’m always learning too.

Why not use a State Management Pattern?

I wanted to keep things simple and using an Event Bus was about as simple as I could make it. It works and it isn’t too nasty.

Homework

If you are hungry for more, how about trying to add in some things I missed out.

Image Validation

Use Laravel’s server side validation to make sure the file is an image, and use DropzoneJS’s validation as a quick client side image validation.

Store File Names In Database

As I mentioned originally I just listed the images from the directory for speed and to keep things simple, but it would be best to use a database instead. Try to implement something using Eloquent.

Verify URL Prop & Respond To List Route Errors

In a real world example you would want to check the url prop in the ImageList.vue component has a value, and also have something happen if the response from the route was an error.

About Paul Robinson

WordPress plugin & theme developer, user of Laravel and learner of React.js. Can usually be found listening to Japanese, and Korean Pop or Vocaloid music while developing.

Advertisment

Leave a Reply

Your email address will not be published. Required fields are marked *