Uploading Files With VueJS and Axios
Part 1 of 7 in Your Guide To Uploading Files with VueJS and AxiosVueJS and Axios GitHub – axios/axios: Promise based HTTP client for the browser and node.js work beautifully together for making HTTP requests. However, uploading files with VueJS and Axios can be a little bit challenging since it requires uploading files through an AJAX type system. I’ll be honest, I hate doing file uploads, but they are a necessity for the majority of applications. This tutorial should be a quick start guide on the process and a few tips I’ve learned when handling file uploads to make your life a little easier.
🚨 Update October, 14, 2021
I’ve went through and updated the code in this tutorial to be more efficient and up-to-date. The code has been tested in Vue 3.0 and Axios v0.21.1. I’ve also made a Github repo that contains all of the examples in this course. Feel free to head over there and download any of the components you wish to use!
Prerequisites
In this tutorial I’m going to be using Axios v0.21.1 and VueJS v3.0.0 for doing the file uploads. On the back end you can use the framework that you want, or language that you want. I’ll talk about using PHP/Laravel to process files and more or less pseudo-code the backend process. The biggest take aways will be how to do the uploading of the file with VueJS and Axios.
I’m also going to assume you are using a modern browser that supports the FormData
object: FormData – Web APIs | MDN. This is what makes the process a whole heck of a lot easier.
Uploading a Single File
So first, we will start with a single file to get things going. I just built a simple component that contains a file input:
<template>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>File
<input type="file" id="file" ref="file" v-on:change="handleFileUpload()"/>
</label>
<button v-on:click="submitFile()">Submit</button>
</div>
</div>
</template>
If you take a look at the input[type="file"]
I added a few attributes. The first is a ref
attribute that gives this input a name. We can now access this input from within VueJS which we will in a second.
UPDATE 10/14/2021: Remove ref=”file”
Previously I had ref="file"
as a key piece of this code. You can do this without ref as well and I’ll make sure to explain how later on when we implement the handleFileUpload()
method:
<template>
<div class="container">
<div>
<h2>Single File</h2>
<hr/>
<label>File
<input type="file" @change="handleFileUpload( $event )"/>
</label>
<br>
<button v-on:click="submitFile()">Submit</button>
</div>
</div>
</template>
The next is the v-on:change="handleFileUpload()"
attribute. When the user uploads a file, this gets called and we can handle the file. We will be implementing this method in the next step.
The last element in the simple form is a button that calls a method submitFile()
when clicked. This is where we will submit our file to the server and we will be implementing this method as well.
Handle User File Upload
The first thing we want to do is add the handleFileUpload()
method to our methods
object which will give us a starting point for implementation:
<script>
export default {
methods: {
handleFileUpload(){
}
}
}
</script>
What we will do in this method is set a local variable to the value of the file uploaded. Now since we are using a modern browser, this will be a FileList
object: FileList – Web APIs | MDN which contains File
objects: File – Web APIs | MDN. The FileList
is not directly editable by the user for security reasons. However, we can allow users to select and de-select files as needed, which we will go through later in the tutorial.
Since we are setting a local piece of data, let’s add that right now to our Vue component. In our data()
method add:
data(){
return {
file: ''
}
},
Now we have something to set in our handleFileUpload()
method! Let’s go back and add the following code to the handleFileUpload()
method:
methods: {
handleFileUpload(){
this.file = this.$refs.file.files[0];
}
}
What this does is when the file has been changed, we set the local file variable to the first File
object in the FileList
on the input[type="file"]
. The this.$refs.file
refers to the ref
attribute on the the input[type="file"]
. This makes it easily accessible within our component.
UPDATE 10/14/2021: Implementation without $refs
You can also implement this functionality without the $refs
by passing the event
to the handleFileUpload()
method:
handleFileUpload( event ){
this.file = event.target.files[0];
},
Submit To Server Through Axios
Now it’s time to submit our file through the server through Axios! On our button, we have a submitFile()
method we need to implement, so let’s add this to our methods:
submitFile(){
},
Now this is where we implement our axios
request.
The first thing we need to do is implement a FormData
object like this:
let formData = new FormData();
Next, what we will do is append the file to the formData
. This is done through the append()
method on the object: FormData.append() – Web APIs | MDN. What we are doing is essentially building a key-value pair to submit to the server like a standard POST request:
formData.append('file', this.file);
We just append the file variable that we have our data stored in. Now I’m not going to go into validations, but before we go any further, if you were to put this in production and the file needed to be added, I’d add a validation here to make sure the file
variable contains an actual file.
Now we can begin to make our axios
request! We will be doing this through the post()
method. If you look at their API (GitHub – axios/axios: Promise based HTTP client for the browser and node.js), you see the post()
method contains 3 parameters. The third parameter is a config for the request which is awesome because we can add other headers to it.
Our completed request should look like:
axios.post( '/single-file',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
The first parameter is the URL we will be POSTing to. For this example, I have a URL set up on my server which is /single-file
. The next parameter is a key-value store of the data we are passing. This is our FormData()
which we built to have our file. The third parameter is probably the key to making this all work. This is adding the multipart/form-data
header we need to send the file to the server.
If you are used to file uploading, this is usually an attribute on the form you are submitting like <form enctype="multipart/form-data"></form>
. Without this header, the POST request will ignore the file.
Now with the rest of our request, we process a callback method on a successful request which can be used to display a notification and we process a callback on failure which can be used to alert the user of an unsuccessful upload.
On the server side, you can access the file through the key of file
which is the first parameter of the formData.append('file', this.file);
method.
In PHP it’d be: $_FILES['file']
and in Laravel, you can use the Request
facade and access it through Request::file('files')
and do whatever server side processing you need.
Our SingleFile.vue
component used for testing looks like this:
<template>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>File
<input type="file" id="file" ref="file" v-on:change="handleFileUpload()"/>
</label>
<button v-on:click="submitFile()">Submit</button>
</div>
</div>
</template>
<script>
export default {
/*
Defines the data used by the component
*/
data(){
return {
file: ''
}
},
methods: {
/*
Submits the file to the server
*/
submitFile(){
/*
Initialize the form data
*/
let formData = new FormData();
/*
Add the form data we need to submit
*/
formData.append('file', this.file);
/*
Make the request to the POST /single-file URL
*/
axios.post( '/single-file',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
},
/*
Handles a change on the file upload
*/
handleFileUpload(){
this.file = this.$refs.file.files[0];
}
}
}
</script>
Github Component
If you’d like a Vue 3 compatible component for a single file upload, check out the component on Github. Feel free to submit any updates you’d like!
The next section, we will handle multiple files. This isn’t anything super different, but I’ll point out the changes!
Uploading Multiple Files
Handling multiple files is very similar to a single file. What we will do is begin with a template that looks like this in our Vue component:
<template>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>Files
<input type="file" id="files" ref="files" multiple v-on:change="handleFileUploads()"/>
</label>
<button v-on:click="submitFiles()">Submit</button>
</div>
</div>
</template>
Besides the ref
attribute name change and the id
changing to files
, the most important attribute is we added multiple
to our input[type="file”]
. This allows the user to cmd (ctrl) + click to select multiple files at once. A super slick way to upload files. In the next section we will be allowing the user to remove files and select more files if they made a mistake 😉 but for now, it’s super slick.
Multiple File handleFileUploads() method
This is very similar to an individual file, except we will be adding all of the files to our array if the user selects more than one. First, let’s add our data store to our Vue component and give it variable named files
:
/*
Defines the data used by the component
*/
data(){
return {
files: ''
}
},
Now we have a local variable to store our files to. We can now do our handleFileUploads()
method:
/*
Handles a change on the file upload
*/
handleFilesUpload(){
this.files = this.$refs.files.files;
}
What this does is grab all of the files in the FilesList
from our files upload and stores it locally.
UPDATE 10/14/2021: Without $refs
Similar to the single file component, you can upload multiple files without the $refs attribute. Your template should look like:
<template>
<div class="container">
<div>
<h2>Multiple Files</h2>
<hr/>
<label>Files
<input type="file" multiple @change="handleFileUploads( $event )"/>
</label>
<br>
<button v-on:click="submitFiles()">Submit</button>
</div>
</div>
</template>
The handleFilesUpload()
method should also be implemented like this, with the event
being passed as the only parameter:
handleFileUpload( event ){
this.files = event.target.files;
},
Implement submitFiles() method
We are ready to submit all of our files to the server now! First, let’s add our submitFiles()
method to the methods array:
/*
Submits all of the files to the server
*/
submitFiles(){
},
Like in the last method, we will initialize the FormData()
object first:
/*
Initialize the form data
*/
let formData = new FormData();
Now, what we will do is loop over all of the files selected and add them to the files
array we are going to submit to the server. The files
array will be a key in the formData()
object we will be sending to the server:
/*
Iteate over any file sent over appending the files
to the form data.
*/
for( var i = 0; i < this.files.length; i++ ){
let file = this.files[i];
formData.append('files[' + i + ']', file);
}
We are now ready to send our files to the server through Axios:
/*
Make the request to the POST /multiple-files URL
*/
axios.post( '/multiple-files',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
There we go! Now we are allowing users to upload multiple files using Axios and VueJS through an AJAX call.
On the server side, you can access the files through the key of files
which is the first parameter of the formData.append('files[' + i + ']', file);
method.
In PHP it’d be: $_FILES['files']
and in Laravel, you can use the Request
facade and access it through Request::file('files')
and do whatever server side processing you need. You can loop through all of the files now to allow for multiple uploads.
Our MultipleFiles.vue
component should look like:
<template>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>Files
<input type="file" id="files" ref="files" multiple v-on:change="handleFilesUpload()"/>
</label>
<button v-on:click="submitFiles()">Submit</button>
</div>
</div>
</template>
<script>
export default {
/*
Defines the data used by the component
*/
data(){
return {
files: ''
}
},
methods: {
/*
Submits all of the files to the server
*/
submitFiles(){
/*
Initialize the form data
*/
let formData = new FormData();
/*
Iteate over any file sent over appending the files
to the form data.
*/
for( var i = 0; i < this.files.length; i++ ){
let file = this.files[i];
formData.append('files[' + i + ']', file);
}
/*
Make the request to the POST /multiple-files URL
*/
axios.post( '/multiple-files',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
},
/*
Handles a change on the file upload
*/
handleFilesUpload(){
this.files = this.$refs.files.files;
}
}
}
</script>
The next step we will allow users to edit the files they have selected so they don’t accidentally upload a file they don’t want.
Github Component
We also created a Github component for handling multiple file uploads. This can be used in your apps and is compatible with Vue 3!
Allowing Users to Edit Selected Files Before Uploading
When uploading multiple files, it’s very common that you accidentally select a file you do NOT want to upload. This sounds simple enough to resolve until you find out you can’t directly edit the FileList
object for security reasons. However, you can transform it and edit the new list as an array and allow users to change the files they want uploaded.
First, let’s reuse the template from the multiple files component:
<template>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>Files
<input type="file" id="files" ref="files" multiple v-on:change="handleFilesUpload()"/>
</label>
<button v-on:click="submitFiles()">Submit</button>
</div>
</div>
</template>
Hide File Input
The first thing we will do is hide the actual file input. This is because we will be making a simple design interface to allow users to select the files they want. I just added a style tag that moves the input off of the screen. This is because it’s a security issue to trigger a click on the hidden file input.
<style>
input[type="file"]{
position: absolute;
top: -500px;
}
</style>
Next, I added a button that triggers a click on the input:
<div class="large-12 medium-12 small-12 cell">
<button v-on:click="addFiles()">Add Files</button>
</div>
So when this is clicked, we trigger a click on the file element. We need to implement the addFiles()
method in our Vue component like this:
addFiles(){
this.$refs.files.click();
}
This will fire a click on the files input and the user will be prompted with a file selection box where they can select the files we want.
UPDATE 10/14/2021: Remove $refs
You can also remove the $refs
attribute when uploading multiple files as well. Just make sure your handleFilesUpload()
method looks like:
/*
Handles the uploading of files
*/
handleFilesUpload( event ){
let uploadedFiles = event.target.files;
/*
Adds the uploaded file to the files array
*/
for( var i = 0; i < uploadedFiles.length; i++ ){
this.files.push( uploadedFiles[i] );
}
},
We will go through the above method more in the next section. Also, make sure your addFiles()
method looks like:
addFiles(){
document.getElementById('select-files').click();
},
Implement handleFilesUpload()
This is where things get a little bit different. Like the first two examples, we will add a local variable to add files to:
data(){
return {
files: []
}
},
We want this as an array so we can push files onto it.
Now, when the user selects some files to upload, we will push them on our local files
variable:
let uploadedFiles = this.$refs.files.files;
/*
Adds the uploaded file to the files array
*/
for( var i = 0; i < uploadedFiles.length; i++ ){
this.files.push( uploadedFiles[i] );
}
We do this through a loop instead of pushing the entire chunk onto the files
array because otherwise we’d have groups based on what was selected. You can add validations here as well so the user doesn’t upload the same file multiple times if you want as well.
Display Currently Uploaded Files
In this use case, we want users to remove files they updated by accident, so we need to display the currently uploaded files.
To do that, we will head back into our template and add the following code:
<div class="large-12 medium-12 small-12 cell">
<div v-for="(file, key) in files" class="file-listing">{{ file.name }} <span class="remove-file" v-on:click="removeFile( key )">Remove</span></div>
</div>
<br>
What this does is iterate over all of the files we’ve currently added and displays them to the user. A couple things to note.
First, the v-for="(file, key) in files"
. What this does is iterate over the files that we’ve uploaded and grabs the key which is the index of the file in the files array and the file itself. We then display the name of the file with: {{ file.name }}
which is part of the individual file object. There’s more information in the object which is documented here: File – Web APIs | MDN
Next, we add a removeFile(key)
method which will remove the file from the file array. When the file is removed, the reactive nature of VueJS will update our listing.
Implement removeFile() Method
This method will remove the file from our uploaded files array. First, let’s add the method to our methods
array:
removeFile( key ){
}
The method accepts the key in the files array of the file we are removing. The full implementation of this method will be:
removeFile( key ){
this.files.splice( key, 1 );
}
What this does is splice the files array at the index of the file we are removing and remove 1 entry from the array. When we do this, our list will re-render through VueJS keeping everything in sync. Since we are using a local files
array, we can modify this at will. The next and final thing we have to do is submit our files to the server that the user has selected!
Submit Files To Server
From here, we’ve allowed the user to modify the files they have selected, we just have to allow them to submit to the server for processing.
First, let’s add the submitFiles()
method to our methods object:
submitFiles(){
}
Like the rest of the examples, let’s first create our FormData()
object:
/*
Initialize the form data
*/
let formData = new FormData();
Now, let’s add all of the chosen files to the form data:
/*
Iterate over any file sent over appending the files
to the form data.
*/
for( var i = 0; i < this.files.length; i++ ){
let file = this.files[i];
formData.append('files[' + i + ']', file);
}
This iterates over the files that the user has selected and prepares to submit them to the server.
Now, we can run the axios.post()
method to submit the files to our endpoint:
axios.post( '/select-files',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
This sends all of our form data to the server with the files that the user has uploaded! If you were to run this as an example, you can see that after you remove a file, it’s no longer sent to the server.
Like before, on the server side, you can access the files through the key of files
which is the first parameter of the formData.append('files[' + i + ']', file);
method.
When using Laravel and the Request
facade, you can access the selected files the user has uploaded with the following method: Request::file('files')
. In straight up PHP it’d be $_FILES['files']
. You can now do any processing you want!
Our SelectFiles.vue
component should look like:
<style>
input[type="file"]{
position: absolute;
top: -500px;
}
div.file-listing{
width: 200px;
}
span.remove-file{
color: red;
cursor: pointer;
float: right;
}
</style>
<template>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>Files
<input type="file" id="files" ref="files" multiple v-on:change="handleFilesUpload()"/>
</label>
</div>
<div class="large-12 medium-12 small-12 cell">
<div v-for="(file, key) in files" class="file-listing">{{ file.name }} <span class="remove-file" v-on:click="removeFile( key )">Remove</span></div>
</div>
<br>
<div class="large-12 medium-12 small-12 cell">
<button v-on:click="addFiles()">Add Files</button>
</div>
<br>
<div class="large-12 medium-12 small-12 cell">
<button v-on:click="submitFiles()">Submit</button>
</div>
</div>
</template>
<script>
export default {
/*
Defines the data used by the component
*/
data(){
return {
files: []
}
},
/*
Defines the method used by the component
*/
methods: {
/*
Adds a file
*/
addFiles(){
this.$refs.files.click();
},
/*
Submits files to the server
*/
submitFiles(){
/*
Initialize the form data
*/
let formData = new FormData();
/*
Iteate over any file sent over appending the files
to the form data.
*/
for( var i = 0; i < this.files.length; i++ ){
let file = this.files[i];
formData.append('files[' + i + ']', file);
}
/*
Make the request to the POST /select-files URL
*/
axios.post( '/select-files',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
},
/*
Handles the uploading of files
*/
handleFilesUpload(){
let uploadedFiles = this.$refs.files.files;
/*
Adds the uploaded file to the files array
*/
for( var i = 0; i < uploadedFiles.length; i++ ){
this.files.push( uploadedFiles[i] );
}
},
/*
Removes a select file the user has uploaded
*/
removeFile( key ){
this.files.splice( key, 1 );
}
}
}
</script>
There ya go! You can now allow users to adjust their mistakes if they select a file they don’t want to upload.
Github Component
Yup! We made another component that allows the user to edit their file uploads before submitting to the server. Check it out and let us know your thoughts!
Gotchas and Recommendations
A few things to point out when uploading using FormData
.
Adding additional POST data to the Request
You can always include more information than just files with your post. When you build your FormData
you can add additional text or other fields like this:
/*
Initialize the form data
*/
let formData = new FormData();
/*
Iteate over any file sent over appending the files
to the form data.
*/
for( var i = 0; i < this.files.length; i++ ){
let file = this.files[i];
formData.append('files[' + i + ']', file);
}
/*
Additional POST Data
*/
formData.append('first_name', 'Dan');
formData.append('last_name', 'Pastori');
The first_name
and last_name
fields will be accessible on the server just like a normal post request!
Arrays with FormData()
Now since we are configuring our request before we send it to the sever, arrays get accessed differently. You will either need to account for this when building your FormData
object. In VueJS, when working with arrays, you can’t just do:
this.coolData = ['one', 'two', 'three'];
formData.append('cool_data', this.coolData);
or you will get an [object Object]
on the server side. You can either iterate over your cool data and push it on a nicely organized array or you can do a JSON.stringify()
method on the data which will convert it to JSON before sending it to the server.
this.coolData = ['one', 'two', 'three'];
formData.append('cool_data', JSON.stringify( this.coolData ) );
You will just need to decode it before you can access it. In PHP, that method would be json_decode($json)
.
Clearing local files on success
When axios returns success, another quick tip is to reset your local files array back to nothing. This makes sure that if you are doing things in a single page application type way or any AJAX driven way, the user who initially submitted the files doesn’t try to re-send a different group of files and gets the first group of files that still exist in the array sent along as well. You can simply clear your local files like this:
this.files = '';
Conclusion
After running through this a few times, uploading files with VueJS and Axios through AJAX becomes a little easier to grasp! Hopefully this tutorial has helped a little. There is always room for expansion and you can do progress uploads and other cool features.
This type of file uploading process comes in handy when developing an API Driven Application, especially when using VueJS. We are working on a book that will tie in this feature along with a whole bunch more API Driven ideals. We will go start to implementation to launch of an API Driven Application and point out common gotchas and tips. Sign up here to stay in the loop as the book progresses: Server Side Up General List