I'm using a multi-step form in Vue.js to send multiple files to a Laravel backend. The form gives the user the ability to add multiple users to the database. Each user requires a file to be uploaded along with it.
Files are initially stored in state using Vuex. Each file is pushed to a files array in store.js
When the user submits the form, the files array looks as follows:
When the user submits the form I'm adding all the individual form data including the files array to a new FormData() object like so:
let fd = new FormData();
// append each file
for( var i = 0; i < store.state.files.length; i++ ){
let file = store.state.files[i];
fd.append('files[' + i + ']', file);
console.log(file);
}
// append rest of form data
fd.append('appointments', store.state.appointments);
fd.append('business_details', store.state.business_details);
fd.append('business_names', store.state.business_names);
fd.append('directors', store.state.directors);
fd.append('share_amount', store.state.share_amount);
fd.append('shareholders', store.state.shareholders);
Once all the form data is added I use Axios to send the form data to my Laravel backend.
axios.post('/businesses', fd, {
headers: {
'content-type': 'multipart/form-data'
}
})
.then(response => {
console.log(response);
this.completeButton = 'Completed';
})
.catch(error => {
console.log(error)
this.completeButton = 'Failed';
})
Inside my Laravel BusinessController I then want to Log::debug($_FILES) to see what files were sent along but all I get is an empty array.
[2018-10-05 16:18:55] local.DEBUG: array (
)
I've checked that the headers I'm sending includes 'multipart/form-data' and that I'm appending all my form data to new FormData() but I cannot figure out why the server receives an empty $_FILES array.
UPDATE 1:
If I Log::debug($request->all()); I get:
If I try to store the objects in that file like so:
foreach ($request->input('files') as $file) {
$filename = $file->store('files');
}
I get the following error:
[2018-10-06 09:20:40] local.ERROR: Call to a member function store() on string
Try this case:
var objectToFormData = function (obj, form, namespace) {
var fd = form || new FormData();
var formKey;
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (namespace) {
formKey = namespace + '[' + property + ']';
} else {
formKey = property;
}
// if the property is an object, but not a File,
// use recursivity.
if (typeof obj[property] === 'object' && !(obj[property] instanceof File)) {
objectToFormData(obj[property], fd, property);
} else {
// if it's a string or a File object
fd.append(formKey, obj[property]);
}
}
}
return fd;
};
Reference : objectToFormData
Axios :
axios.post('/businesses', fd, {
transformRequest: [
function (data, headers) {
return objectToFormData(data);
}
]
})
.then(response => {
console.log(response);
this.completeButton = 'Completed';
})
.catch(error => {
console.log(error)
this.completeButton = 'Failed';
})
Try changing this line:
fd.append('files[' + i + ']', file);
To this:
fd.append('files[]', file);
Related
I am trying to post a file into API my file is shown in the console, but in API payloads it is showing [object file] on uploading.
This is a function for taking file
handleFileChange = (e) => {
const files = e.target.files[0];
this.setState({ document: files });
console.log("Guru4666", this.state.document);
};
The state is then pushed into an object called award value.
SubmitAward() {
var awardValue = {
title: this.state.awardTitle,
award_img: this.state.document,
};
console.log("awardValue", awardValue);
this.state.awarddata.push(awardValue);
}
Then the API is called.
instructor_register() {
var titlearr = [];
var awardimgarr= [];
const UserToken = localStorage.getItem("UserToken");
this.state.awarddata.map((Data) => {
console.log("AwardDataa", Data);
titlearr.push(Data.title);
awardimgarr.push(Data.award_img);
});
const formdata = new FormData();
formdata.append("first_name",this.state.firstname)
formdata.append("instructor_award_name",titlearr)
formdata.append("instructor_award_img",awardimgarr)
axios({
method: "post",
url: `${API_AUTH_URL}instructor-register`,
data: formdata,
headers: { Authorization: "Bearer" + UserToken },
})
.then((response) => {
//this.setState({ modalVisibleLoader: false })
console.log("response.....", response.data);
}
Payload is
instructor_award_img: [object File]
You are attempting to append an array to the formdata object. This isn't a supported data type, so it gets converted to a string.
Supported data types are strings and blobs (files are a type of blob).
Append the file instead.
If you want to append multiple files, do it by looping over the array and appending each file in turn.
I just updated my User model with "avatar" field so I can upload a photo. I used a PUT method already configured and just added avatar to it. In postman the file upload (form-data) it works just fine, but when trying to upload it using axios from vue.js it doesn't work. I tried in many ways, the last one, I tried to send the request as multi form data.
async saveChanges() {
const fd = new FormData();
fd.append("id", this.$auth.user().id);
fd.append("username", this.$auth.user().username);
fd.append("email", this.user.email);
fd.append("firstName", this.$auth.user().firstName);
fd.append("lastName", this.$auth.user().lastName);
fd.append("isAdmin", this.$auth.user().isAdmin);
fd.append("password", this.user.password);
fd.append("confirmpass", this.user.confirmpass);
fd.append("avatar", this.selectedFile, this.selectedFile.name);
fd.append("_method", "put");
try {
await this.axios.put(`/users/${this.$auth.user().id}`, {
fd
}).then((res) => {
console.log(res);
});
} catch (err) {
console.error(err);
}
}
After i choose the file, it is available, but I am unable to send it trough my method. Should i create another request just for updating the avatar or is it possible to solve this?
Try this
axios.put('/users/${this.$auth.user().id', fd, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
Pass your method as post method and define put in form data
formData.append('_method', 'PUT') .
async handleSubmit() {
let formData = new FormData();
formData.append('image', this.file);
formData.append('title', this.form.title);
formData.append('_method', 'PUT')
try {
const response = await axios.post(`image-update/${this.$route.params.id}`, formData)
this.$router.push('/');
console.log(response);
} catch (e) {
this.error = 'Something error found !';
}
}
My previous wrong code that return empty result in $request->all()
let data = new FormData()
data.append('message', 'AnyMessage')
Then I change it as follows and it's working fine -
let data = "message=AnyMessage"
I have an array of the object, each object contains some properties of type string and a file.
This is how my array looks like:
const args = [
{
name: 'presentation',
price: 9.99,
tags: 'invest, finance, business',
file: File,
thumbnail: File
},
{
name: 'presentation2',
price: 20.99,
tags: 'invest, finance, business',
file: File,
thumbnail: File
}
]
const headers = {
headers: {
Authorization: `Bearer ${token}`
}
};
My goal is to send this whole array of the object to the Express Server. there I will save information to the Mongo and upload the file to S3.
But Currently, I am unable to get the file stream on the server when I send this in a POST request,
even the whole req.body is an empty object when I console.log it
axios.post(`${slidesRoute}/createSlides`, args, headers);
Alternatively, I tried to put everything into FormData.
for example:
let formData = new FormData();
selectedFiles.forEach((selectedFile, i) => {
formData.append(`name_${i}`, selectedFile.name);
formData.append(`price_${i}`, selectedFile.price);
formData.append(`tags_${i}`, selectedFile.tags);
formData.append(`file_${i}`, selectedFile.file);
formData.append(`thumbnail_${i}`, selectedFile.thumbnail);
});
axios.post(`${slidesRoute}/createSlides`, formData, headers);
Then In the backend. I am unable to catch these files using multer. Because the filenames in form data are being generated dynamically like
file_1, file_2 file_3,...
But multer expects the exact filename to be passed
for example multer().array('file')
so in my second approach I am unable to catch files using Multer
You can't include file inside JSON. File is not JSON-serializable. Ideally you should have a separate endpoint to accept your files, send them with content-type: multipart/form-data, receive unique id for that file stored on the server, then send your JSON with ids instead of files.
As a chaper alternative you can Base64 encode your files into a string, insert this string in your request, and then decode it back on the receiving side
FormData key values are array by default
in your code Just change
formData.append(`file_${i}`, selectedFile.file);
to
formData.append(`file`, selectedFile.file);
You should be able to receive the file array in server side using multer().array('file')
Sample below demonstrating FormData key values array behavior
const formData = new FormData();
formData.append('key1', 'value1');
formData.append('key1', 'value2');
formData.append('key2', 'value1');
formData.forEach(function(value, key){
console.log(key, value);
});
I use FormData to send a number of files - and then on the node.js server I use Formidable.
I've pasted some partial code below that may help - sorry it's not a smaller sample.
Hope it helps
On the browser side I have:
let form_data = new FormData()
form_data.append('upload_data', file)
$.ajax({
url: '/file/upload' + remote_path + '/' + filename,
type: 'POST',
data: form_data,
processData: false,
contentType: false,
success: (res) => {
...
On the node.js side (apologies for the length..)I have:
const fs = require('fs')
const formidable = require('formidable')
const path = require('path')
...
app.post('/file/upload/*', (req, res) => {
let filename = req.params[0]
let media = path.basename(filename)
let folder = filename.substring(0, filename.length - media.length)
let form = new formidable.IncomingForm()
// form.encoding = 'utf-8'
form.multiples = true
form.uploadDir = path.join(media_folder, folder)
form.keepExtensions = true
form.parse(req, (err, fields, files) => {
if (err) {
fail(res, 'failed to upload')
} else {
success(res)
}
})
form.on('fileBegin', (name, file) => {
const [fileName, fileExt] = file.name.split('.')
file.path = path.join(form.uploadDir, `${fileName}_${new Date().getTime()}.${fileExt}`)
})
form.on('file', (field, file) => {
fs.rename(file.path, path.join(form.uploadDir, file.name), (err) => {
if (!res.headersSent) { // Fix since first response is received
fail(res, 'an error has occured with form upload' + err)
}
})
})
form.on('error', (err) => {
fail(res, 'an error has occured with form upload' + err)
})
form.on('aborted', (err) => {
fail(res, 'Upload cancelled by browser')
})
})
I've been struggling with this problem for sometime now and its very frustrating. I've been trying to use Javascript's FormData to upload a file and it seem the file doesn't get sent to the server.
uploadCoverImages() {
const form = new FormData();
const files = this.coverImagesFiles; // const files is now a Files[] object
for (let i = 0; i < files.length; i++) {
const imageFile = files[i];
console.log(imageFile); // return the right file data
form.append('image', imageFile, imageFile.name);
this.http.post(this.uploadUrl, form, { headers: this.headers }).subscribe(
res => {
console.log('res : ' + res);
},
err => {
console.log('err: ' + err); // always get an error saying file is empty
}
);
}
}
This is my http header code:
getFileUploadHeader() {
const token = sessionStorage.getItem('token');
const headers = new HttpHeaders({ // Angular's HttpHeader
'Content-Type': 'multipart/form-data',
'Accept': 'application/json', // some google searches suggested i drop this but it still doesnt work
'Authorization': 'Bearer ' + token
});
return headers;
}
My PHP code (Laravel 5) is expecting a file with key like this:
public function imageUpload(Request $request)
{
if($request->hasFile('image'))
{
return Reponse::json('file ok');
}
else
{
return Response::json('no file');
}
}
I just dont know why form data doesnt send the file or if am doing something wrong at the server side. How do i get this work. Please help!!!
I'm trying to use the native Fetch and FormData APIs to upload multiple files at once to the server but I can't for the life of me get it to work. Here's what I've got:
// acceptedFiles are File objects coming from `react-dropzone`.
function handleSubmit(acceptedFiles) {
const data = new FormData();
for (const file of acceptedFiles) {
data.append('files', file, file.name);
}
return fetch('https://example.com/api/upload', {
method: 'POST',
body: data,
});
}
But what my Rails server receives is this:
Parameters: {"files"=>#
<ActionDispatch::Http::UploadedFile:0x00007feb347becc0 #tempfile=#
<Tempfile:/var/folders/kl/y1jrp7zs55sbx075jjjl3p280000gn/T/RackMultipart201
71112-6486-1ftkufy.mp4>, #original_filename="SampleVideo_1280x720_5mb.mp4",
#content_type="video/mp4", #headers="Content-Disposition: form-data;
name=\"files\"; filename=\"SampleVideo_1280x720_5mb.mp4\"\r\nContent-Type:
video/mp4\r\n">}
In other words, it looks like files is actually just one file. But the docs for FormData say that append should append multiple files.
So what's going wrong?
The solution was to change files to files[]:
// acceptedFiles are File objects coming from `react-dropzone`.
function handleSubmit(acceptedFiles) {
const data = new FormData();
for (const file of acceptedFiles) {
data.append('files[]', file, file.name);
}
return fetch('https://example.com/api/upload', {
method: 'POST',
body: data,
});
}
For sending multiple files i have done it a little bit different because my php api endpoint says there was only one file if i send it with formdata.append('files[]', file)
<input type="file" multiple data-sender="{{ user.hashIdentifier }}">
/** #type {HTMLInputElement} */
const input = document.querySelector('input[type=file]');
input.addEventListener('input', function listener(event){
if(input.files.length > 0){
const formData = new FormData();
for(let i = 0; i < input.files.lenght; i++){
formData.append(`file_${i}`, input.files[i]);
}
// my endpoint should know who sends the file
formData.append('identify', input.dataSet.sender);
sendData(formData);
} else {
console.error('no files selected');
}
});
/**
* sending the formData Object with all files to the server
* #param {FormData} formData
*/
function sendData(formData) {
const url = '/api/saveFiles';
const options = {
method: 'POST',
body: formData
};
fetch(url, options)
.fetch((response) => {
if(!response.ok) {
throw Error(response.statusText);
}
return response.json();
})
.fetch((data) => {
console.log('success', data);
})
.catch((error) => {
console.error(error);
})
}
my php is a symfony project and looks like that:
/**
* #param Request $request (inject symfonys http request)
* #param FileService $fileService (inject my own FileService)
* #returns Response (symfony http Response)
*/
#[Route('/api/saveFiles', name: 'api_saveFiles', methods: ['POST'])]
public function api_saveFiles(Request $request, FileService $fileService): Response
{
$statusCode = 409; // Conflict
$result = array();
/** #var FileBag */
$fileBag = $request->files;
if($fileBag->count() > 0) {
$statusCode=200;
$result['numberOfFiles'] = $fileBag->count();
$result['keys'] = $fileBag->keys();
$result['infos'] = array();
foreach($fileBag->keys() as $key){
try {
$file = $fileBag->get($key);
$fileInfo = $fileService->saveFile($file); // saves the file and gives back some infos
$result['infos'][$fileInfo];
} catch(Exception $ex) {
// TODO: handle errors
}
}
}
return new JsonResponse($result, $statusCode);
}
in the attemps i tried it with formData.append('files[]', file); php has only captured the last file.
// acceptedFiles are File objects coming from `react-dropzone`.
function handleSubmit(acceptedFiles) {
const data = new FormData();
for (const [index, file] of acceptedFiles) {
data.append('files' + index, file, file.name);
}
return fetch('https://example.com/api/upload', {
method: 'POST',
body: data, enter code here
});
}