Uploading multiple files with Fetch and FormData APIs - javascript

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
});
}

Related

read file with php that sent by axios with content-type : image/jpeg [duplicate]

Using raw HTML when I post a file to a flask server using the following I can access files from the flask request global:
<form id="uploadForm" action='upload_file' role="form" method="post" enctype=multipart/form-data>
<input type="file" id="file" name="file">
<input type=submit value=Upload>
</form>
In flask:
def post(self):
if 'file' in request.files:
....
When I try to do the same with Axios the flask request global is empty:
<form id="uploadForm" enctype="multipart/form-data" v-on:change="uploadFile">
<input type="file" id="file" name="file">
</form>
uploadFile: function (event) {
const file = event.target.files[0]
axios.post('upload_file', file, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
If I use the same uploadFile function above but remove the headers json from the axios.post method I get in the form key of my flask request object a csv list of string values (file is a .csv).
How can I get a file object sent via axios?
Add the file to a formData object, and set the Content-Type header to multipart/form-data.
var formData = new FormData();
var imagefile = document.querySelector('#file');
formData.append("image", imagefile.files[0]);
axios.post('upload_file', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
Sample application using Vue. Requires a backend server running on localhost to process the request:
var app = new Vue({
el: "#app",
data: {
file: ''
},
methods: {
submitFile() {
let formData = new FormData();
formData.append('file', this.file);
console.log('>> formData >> ', formData);
// You should have a server side REST API
axios.post('http://localhost:8080/restapi/fileupload',
formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function () {
console.log('SUCCESS!!');
})
.catch(function () {
console.log('FAILURE!!');
});
},
handleFileUpload() {
this.file = this.$refs.file.files[0];
console.log('>>>> 1st element in files array >>>> ', this.file);
}
}
});
https://codepen.io/pmarimuthu/pen/MqqaOE
If you don't want to use a FormData object (e.g. your API takes specific content-type signatures and multipart/formdata isn't one of them) then you can do this instead:
uploadFile: function (event) {
const file = event.target.files[0]
axios.post('upload_file', file, {
headers: {
'Content-Type': file.type
}
})
}
Sharing my experience with React & HTML input
Define input field
<input type="file" onChange={onChange} accept ="image/*"/>
Define onChange listener
const onChange = (e) => {
let url = "https://<server-url>/api/upload";
let file = e.target.files[0];
uploadFile(url, file);
};
const uploadFile = (url, file) => {
let formData = new FormData();
formData.append("file", file);
axios.post(url, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
}).then((response) => {
fnSuccess(response);
}).catch((error) => {
fnFail(error);
});
};
const fnSuccess = (response) => {
//Add success handling
};
const fnFail = (error) => {
//Add failed handling
};
This works for me, I hope helps to someone.
var frm = $('#frm');
let formData = new FormData(frm[0]);
axios.post('your-url', formData)
.then(res => {
console.log({res});
}).catch(err => {
console.error({err});
});
this is my way:
var formData = new FormData(formElement);
// formData.append("image", imgFile.files[0]);
const res = await axios.post(
"link-handle",
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
How to post file using an object in memory (like a JSON object):
import axios from 'axios';
import * as FormData from 'form-data'
async function sendData(jsonData){
// const payload = JSON.stringify({ hello: 'world'});
const payload = JSON.stringify(jsonData);
const bufferObject = Buffer.from(payload, 'utf-8');
const file = new FormData();
file.append('upload_file', bufferObject, "b.json");
const response = await axios.post(
lovelyURL,
file,
headers: file.getHeaders()
).toPromise();
console.log(response?.data);
}
There is an issue with Axios version 0.25.0 > to 0.27.2 where FormData object in a PUT request is not handled correctly if you have appended more than one field but is fine with one field containing a file, POST works fine.
Also Axios 0.25.0+ automatically sets the correct headers so there is no need to specify Content-Type.
For me the error was the actual parameter name in my controller... Took me a while to figure out, perhaps it will help someone. Im using Next.js / .Net 6
Client:
export const test = async (event: any) => {
const token = useAuthStore.getState().token;
console.log(event + 'the event')
if (token) {
const formData = new FormData();
formData.append("img", event);
const res = await axios.post(baseUrl + '/products/uploadproductimage', formData, {
headers: {
'Authorization': `bearer ${token}`
}
})
return res
}
return null
}
Server:
[HttpPost("uploadproductimage")]
public async Task<ActionResult> UploadProductImage([FromForm] IFormFile image)
{
return Ok();
}
Error here because server is expecting param "image" and not "img:
formData.append("img", event);
public async Task<ActionResult> UploadProductImage([FromForm] IFormFile image)

Formidable returns null and two empty objects even though I am passing the correct FormData File image

I have the following code that happens on submit of a form
data contains the File, and it console's just fine. When I pass it to formidable in next JS it returns empty objects for Field and Files, and can't seem to figure out why.
const formData = new FormData();
formData.append("image", data.picture[0], data.picture[0].name);
console.log(data.picture[0]);
console.log(formData);
console.log(formData.get("image"));
const res = await fetch("../api/image", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
import formidable from "formidable";
// first we need to disable the default body parser
export const config = {
api: {
bodyParser: false,
},
};
export default async function image(req, res) {
const form = new formidable.IncomingForm();
form.uploadDir = "./";
form.keepExtensions = true;
form.parse(req, (err, fields, files) => {
console.log(err);
console.log(fields);
console.log(files);
});
//console.log(form);
// cloudinary.uploader.upload(`${body}`, function (error, result) {
// console.log(result, error);
// });
try {
// const result = req.body;
res.status(200).send({ message: "hello world" });
} catch (error) {
console.error(error);
res.status(error.requestResult.statusCode).send(error.message);
}
}
Ended up using axios, no idea why it works over fetch, and I tried the content type change in fetch as well
var formData = new FormData();
formData.append("image", data.picture[0], data.picture[0].name);
axios.post('../api/image', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})

How to send a file in array of object to express server?

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')
})
})

Sending multiple files to Laravel backed using Vue.js

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);

How to upload a file to SharePoint Online library in Angular 4 application?

I am working Angular SPA application.
I am using SP.js & SP.Requestor.js which is useful to upload the same but none seems to be working of Angular 4 App.
UploadFile(event: any) {
let fileList: FileList = event.target.files;
let fileName;
if (fileList.length != 0) {
this.getFileBuffer(fileList[0]).then(result => {
this.uploadFile(result, fileList[0].name, "POC").then(result => {
alert("added");
});
});
// this.fileUpload(fileList[0], "RetrievePOC", fileList[0].name);
}
}
getFileBuffer(file) {
return new Promise((resolve, reject) => {
var myReader: FileReader = new FileReader();
myReader.onload = function (e) {
resolve(myReader.result);
//resolve(e.target);
}
myReader.onerror = function (e) {
//deferred.reject(e.target.error);
}
//myReader.readAsArrayBuffer(file);
myReader.readAsBinaryString(file);
//resolve(file);
//return deferred.promise();
});
};
uploadFile(file, fileName, libraryName) {
return new Promise((resolve, reject) => {
// Construct the endpoint - The GetList method is available for SharePoint Online only.
//var serverRelativeUrlToFolder = "decodedurl='" + "/" + libraryName + "'";
var endpoint = this.siteUrl + "/_api/web/lists/getByTitle('POC')/Files/add('" + fileName + "')";
const headers = {
"accept": "application/json;odata=verbose",
"content-type": "application/json; odata=verbose",
};
//let fileData =this.convertDataBinaryString(file);
this.executeAsync(endpoint, file, headers).then(file => resolve(true)).catch(err => reject(err));
});
}
executeAsync(endPointUrl, data, requestHeaders) {
return new Promise((resolve, reject) => {
// using a utils function we would get the APP WEB url value and pass it into the constructor...
let executor = new SP.RequestExecutor(this.siteUrl);
// Send the request.
executor.executeAsync({
url: endPointUrl,
method: "POST",
body: data,
binaryStringRequestBody: true,
headers: requestHeaders,
success: offset => resolve(offset),
error: err => reject(alert(err.responseText))
});
});
}
Getting following error in executeAsync methond :
ErrorPage.PostMessage: Origin=https://localhost:44316,
Data={"command":"Query","postMessageId":"SP.RequestExecutor3","responseAvailable":false,"errorCode":-1007,"errorMessage":"Correlation
ID: e12d5a9e-b0d6-0000-745f-24b31dd971a6"}
vendor.js?v=T82_qgC1tKr4vAoag-4pr9ch_dUDSit3nEgaqP4H0Ec:12090 ERROR
Error: Uncaught (in promise): undefined
at resolvePromise (vendor.js?v=T82_qgC1tKr4vAoag-4pr9ch_dUDSit3nEgaqP4H0Ec:87020
)
We can use jQuery to upload file, here is a demo for your reference:
HTML:
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js" type="text/javascript"></script>
<input id="getFile" type="file"/><br />
<input id="displayName" type="text" value="Enter a unique name" /><br />
<input id="addFileButton" type="button" value="Upload" onclick="uploadFile()"/>
JS code:
'use strict';
jQuery(document).ready(function () {
// Check for FileReader API (HTML5) support.
if (!window.FileReader) {
alert('This browser does not support the FileReader API.');
}
});
// Upload the file.
// You can upload files up to 2 GB with the REST API.
function uploadFile() {
// Define the folder path for this example.
var serverRelativeUrlToFolder = '/shared documents';
// Get test values from the file input and text input page controls.
var fileInput = jQuery('#getFile');
var newName = jQuery('#displayName').val();
// Get the server URL.
var serverUrl = _spPageContextInfo.webAbsoluteUrl;
// Initiate method calls using jQuery promises.
// Get the local file as an array buffer.
var getFile = getFileBuffer();
getFile.done(function (arrayBuffer) {
// Add the file to the SharePoint folder.
var addFile = addFileToFolder(arrayBuffer);
addFile.done(function (file, status, xhr) {
// Get the list item that corresponds to the uploaded file.
var getItem = getListItem(file.d.ListItemAllFields.__deferred.uri);
getItem.done(function (listItem, status, xhr) {
// Change the display name and title of the list item.
var changeItem = updateListItem(listItem.d.__metadata);
changeItem.done(function (data, status, xhr) {
alert('file uploaded and updated');
});
changeItem.fail(onError);
});
getItem.fail(onError);
});
addFile.fail(onError);
});
getFile.fail(onError);
// Get the local file as an array buffer.
function getFileBuffer() {
var deferred = jQuery.Deferred();
var reader = new FileReader();
reader.onloadend = function (e) {
deferred.resolve(e.target.result);
}
reader.onerror = function (e) {
deferred.reject(e.target.error);
}
reader.readAsArrayBuffer(fileInput[0].files[0]);
return deferred.promise();
}
// Add the file to the file collection in the Shared Documents folder.
function addFileToFolder(arrayBuffer) {
// Get the file name from the file input control on the page.
var parts = fileInput[0].value.split('\\');
var fileName = parts[parts.length - 1];
// Construct the endpoint.
var fileCollectionEndpoint = String.format(
"{0}/_api/web/getfolderbyserverrelativeurl('{1}')/files" +
"/add(overwrite=true, url='{2}')",
serverUrl, serverRelativeUrlToFolder, fileName);
// Send the request and return the response.
// This call returns the SharePoint file.
return jQuery.ajax({
url: fileCollectionEndpoint,
type: "POST",
data: arrayBuffer,
processData: false,
headers: {
"accept": "application/json;odata=verbose",
"X-RequestDigest": jQuery("#__REQUESTDIGEST").val(),
"content-length": arrayBuffer.byteLength
}
});
}
// Get the list item that corresponds to the file by calling the file's ListItemAllFields property.
function getListItem(fileListItemUri) {
// Send the request and return the response.
return jQuery.ajax({
url: fileListItemUri,
type: "GET",
headers: { "accept": "application/json;odata=verbose" }
});
}
// Change the display name and title of the list item.
function updateListItem(itemMetadata) {
// Define the list item changes. Use the FileLeafRef property to change the display name.
// For simplicity, also use the name as the title.
// The example gets the list item type from the item's metadata, but you can also get it from the
// ListItemEntityTypeFullName property of the list.
var body = String.format("{{'__metadata':{{'type':'{0}'}},'FileLeafRef':'{1}','Title':'{2}'}}",
itemMetadata.type, newName, newName);
// Send the request and return the promise.
// This call does not return response content from the server.
return jQuery.ajax({
url: itemMetadata.uri,
type: "POST",
data: body,
headers: {
"X-RequestDigest": jQuery("#__REQUESTDIGEST").val(),
"content-type": "application/json;odata=verbose",
"content-length": body.length,
"IF-MATCH": itemMetadata.etag,
"X-HTTP-Method": "MERGE"
}
});
}
}
// Display error messages.
function onError(error) {
alert(error.responseText);
}

Categories