Ways to upload a file
Most articles showing how to upload a file using JavaScript actually teach how to encode the file contents in Base64 so it can be included in the JSON request. It works, but it's not as efficient as other methods. In this post I'll show how to upload a file using the multipart/FormData method using Vue.js together with axios.
Base64 inside JSON
Advantages:
- No need to manually encode/decode your data in JSON (if using any frontend framework or client library)
- File's content is just another field in the JSON object
Disadvantages:
- Need to encode the file in Base64
- Uses more CPU, more memory, and more network bandwidth (Base64 uses 33% more space than binary)
- Little support from backend frameworks
Multipart
Advantages:
- No need to encode the file in Base64
- Uses less CPU, less memory, and less network bandwidth
- Full support from backend frameworks
Disadvantages:
- Need to manually encode/decode your data in JSON
- File's content is separate from the JSON object
Getting the file
In one way or another, your page will have a file input element that lets the user choose a file. Vue will complain if you try to use v-model
on it because file inputs are readonly, so we usually add an event handler for the change
event.
<template>
<input type="file" @change="selectFile">
</template>
<script>
data: () => ({
photo: null,
description: '',
productId: 0,
}),
methods: {
selectFile(event) {
// `files` is always an array because the file input may be in multiple mode
this.photo = event.target.files[0];
}
}
</script>
Sending the file
File input elements have a files
property that is an array of instances of the File
class. It has some metadata about the selected file and methods to read its contents. Besides that, it can be used directly as a value in a FormData
object. The FormData
class allows one to use JavaScript to build the same request that a plain HTML form would create. You can use a FormData
object as the request's body when using axios, jQuery or even plain XMLHttpRequest
objects.
The following:
const data = new FormData();
data.append('photo', this.photo);
data.append('description', this.description);
data.append('productId', this.productId);
axios.post("/api/photo", data);
Is roughly the same as:
<form method="POST" enctype="multipart/form-data" action="/api/photo">
<input type="file" name="photo"/>
<input type="text" name="description"/>
<input type="text" name="productId">
</form>
If you have complex data as arrays or nested objects, you will have to convert them to JSON manually:
const data = new FormData();
data.append('photo', this.photo);
const json = JSON.stringify({
description: this.description,
productId: this.productId,
});
data.append('data', json);
axios.post("/api/photo", data);
Receiving the file
At the Laravel side, there is full support to handle file uploads transparently using the Request
class. Uploaded files are fields like any other, presented by the framework as instances of the Illuminate\Http\UploadedFile
class. From there on you can read the file's contents or store it somewhere else.
public function savePhoto(Request $request)
{
// Validate (size is in KB)
$request->validate([
'photo' => 'required|file|image|size:1024|dimensions:max_width=500,max_height=500',
]);
// Read file contents...
$contents = file_get_contents($request->photo->path());
// ...or just move it somewhere else (eg: local `storage` directory or S3)
$newPath = $request->photo->store('photos', 's3');
}
If you had complex data that you manually converted to JSON, you need to decode it before use:
public function savePhoto(Request $request)
{
$request['data'] = json_decode($request['data']);
// Validate
$request->validate([
'data.description' => 'required|filled|size:100',
'data.productId' => 'required|int|exists:App\Product,id'
]);
// ...the rest is the same...
}
Top comments (12)
Thanks for this! Very useful. For anyone else who had trouble handling the request data on the Laravel side, I had to set the
Content-Type
header in the Axios post method toapplication/json
before I could decode it in the controller method.Thank a lot for this article.
Performance wise could you kindly tell me how impactful is to process multi-file comparing these two approaches below?
I am eager to read of your point of view about pros and cons of these two way of doing multi-file upload.
Thank again.
Regards.
A lot of React Native examples advise to just append the
photo
form data, as an object:github.com/g6ling/React-Native-Tip...
How would this work since you mentioned every object needs to be stringified?
PS: Their examples don't work for me, so that would confirm. Laravel does not "understand" a "photo object:
{ uri, type, name }
",$request->file('photo')
returnsnull
.Found it.
photo.name
cannot benull, undefined, ...
... If not Laravel does not recognize thephoto
field as anIlluminate\Http\UploadedFile
... Damn it.Thank you!
Thank you!
Thank you!
Thank you!
Thank you!
a thousand of Thank you!
I had two days struggling with this.
I'm glad it was useful to you!
Hello Diogo,
Why is that?
Because when making a request encoded as
multipart/form-data
(which is a requirement for file uploads) all you can send is a flat list ofname=value
pairs - wherevalue
may either be a string or a file.If you try to add an array or an object directly to the
FormData
object, it will be implicitly converted to a string, which is probably not what you want (resulting in, for example,"[object Object]"
or"12,15,28,55"
).what should be
photo
in html?This has been SO useful. I've been trying to make file upload with Inertia work for a few days now and I couldn't find the proper solution.
Thanks so so much.
Thankyou very much!
This help me a lot in my project.
I Salute you brother!
How to Upload pdf, xls, xlsx, doc, docx in Laravel and Vue.js ???