I'm building a React app, and one of the components is creating a FormData object with two fields - one is a file and the other is a string. The FormData is being sent as a PUT method to an express route, and its type is multipart/form-data because there's a file to upload. There I need to get the string valie from the form (called path), then use multer/multer-s3 to upload the file to AWS S3 to the specified path.
I'm stuck on how to do this. I wasn't able to find an answer on how to retrieve a text field from a multipart request. I did see How to Post "multipart/form-data" Form and get the Text field values from the Node.js server? and several similar suggestions, however the suggested answer did not work for me.
On the body-parser website, it says "This does not handle multipart bodies" - so that also means that bodyParser.urlencoded wouldn't help in my case.
React component:
const path = `/somepathhere/`;
const formData = new FormData();
formData.append('file', file);
formData.append('path', path)
// call API handler
apiActions.js:
const response = await fetch(apiUrl, {
method: "PUT",
body: formData
})
server.js:
// other code taken out for brevity
// middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// other code taken out for brevity
app.put('/api/uploadToS3', (req, res) => {
const path = '';
const storage = multerS3({
s3: s3,
bucket: process.env.AWS_S3_BUCKET,
contentType: multerS3.AUTO_CONTENT_TYPE,
cacheControl: 'max-age=31536000',
metadata: function (req, file, cb) {
cb(null, {fieldName: file.fieldname});
},
key: function (req, file, cb) {
cb(null, path + file.originalname)
}
})
const upload = multer({
storage: storage
}).any();
upload(req,res,function(err) {
if(err) {
console.log(err);
return res.end("Error uploading file.");
} else {
res.end("File has been uploaded");
}
});
});
If there is a better way to handle this, I would also like to know. The end goal is that the file should be uploaded to an S3 path dynamically determined in the React app. I tried sending an object with formData and path like such:
const response = await fetch(apiUrl, {
method: "PUT",
body: {formData: formData, path: path}
})
But that gave the error: [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client. Trying to add headers to the fetch request likewise gave various errors.
Related
I'm trying to allow users to upload pictures to my application (React frontend, Node backend). I want to send the uploaded file from the client to the server, then make a request from my server to Firebase cloud functions that will deal with storing the image.
My HTML for the form is as follows:
<div className='LoafGalleryWrap'>
<h1 className='banner'>Illustrious Loafs</h1>
<form>
<label>Add a loaf to the exhibit:
<input type='file' onChange={props.fileSelectedHandler} />
<button onClick={props.fileUploadHandler}>Upload</button>
</label>
</form>
<button onClick={props.toggleDisplayLandingPage}>Back</button>
</div>
I get the file data in my app with the following method. In the console.log, I can see the file data I want to send to my server.
fileUploadHandler(e) {
e.preventDefault();
e.stopPropagation();
let file = this.state.selectedFile;
let formData = new FormData();
formData.append('image', file);
formData.append('name', file.name);
const pic = formData.getAll('image');
console.log(formData)
axios({
url: 'http://localhost:3001/uploadLoaf',
method: 'POST',
data: formData,
headers: { "Content-Type": "multipart/form-data" }
})
.then(respo => console.log(respo))
}
In my server, when I check the request body, there is nothing there. I log it out and simply get an empty object. I've tried using multer and connect-multipart middlewares but none have been successful in showing the form data. What I am doing is similar to what this article suggest (https://www.geeksforgeeks.org/file-uploading-in-react-js/), but I am at a loss for how to get the data sent in my forms. Any guidance much appreciated.
My server route is as follows:
app.post('/uploadLoaf', (req, res) => {
console.log('uploadLoaf endpoint reached');
console.log(req.body);
res.status(201).json('complete')
// axios.post('https://us-central1-sour-loafs.cloudfunctions.net/uploadFile/', 'upload_file', formData, {
// headers: {
// 'Content-Type': 'multipart/form-data'
// }
// })
// .then(result => {
// console.log(result);
// res.send(result);
// })
// .catch(err => {
// console.log(err);
// res.send(err)
// })
});
try to replace the axios function you have with this
axios.post("http://localhost:3001/uploadLoaf", formData, { })
.then(res => { // then print response status
console.log(res.statusText)
})
let me know what you use in the backend and what is the result on res function
I managed to access the FormData file data by using Multer's upload function. My imports and server route configured so that I can access the file data is as follows:
const multer = require("multer");
const upload = multer({
limits:{fileSize: 1000000},
}).single("image");
app.post('/uploadLoaf', (req, res) => {
upload(req, res, (err) => {
if (err) {
res.sendStatus(500);
}
console.log(req.file);
res.send(req.file);
});
});
I am now unsure of how to send this file data to the API in the way the API expects, getting an error stating: "Error: could not handle the request\n". But at least I can now see the data from the form.
I have a ReactJS Application that sends an Axios POST request to a NodeJS express server and expects a response with several fields. Specifically, I am using "form-data" in the backend to handle the data streaming.
Here is my backend response.
var FormData = require('form-data');
app.get('/test', (req, res) => {
// create a new form to send all data back to client
var form = new FormData();
form.append('results_log', 'this is text', { contentType: 'text/plain'});
form.append('results_images', fs.createReadStream('./test_results/result.zip'), { contentType: 'application/zip' });
console.log(form);
form.pipe(res);
});
In the frontend, I can download the "results_images" ZIP file from the formdata server response without any issues, but I cannot get the "results_log" field, which is just a simple string. Every attempt has resulted in a "null".
Below is the frontend code.
axios({
url: 'http://localhost:5000/test',
method: 'GET'
})
.then((response) => {
var info = response;
// this alerts NULL
alert(info.data["results_log"]);
const url = window.URL
.createObjectURL(new Blob([info.data["results_images"]]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'results.zip');
document.body.appendChild(link);
link.click();
})
.catch((error) => console.log(error));
Here is part of the alert of the whole response.data field. As you can see, both fields are present in the response.
Am I doing something wrong when extracting the string field? Any help is appreciated!
I'm trying to update a local JPG image file into an S3 bucket using the REST PUT request and Axios.
I managed to send the PUT request and to get a positive answer from AWS S3 Service but what it's been upload is not a JPG file but a JSON file.
This is the code that I'm using:
//Create the FormData
var data = new FormData();
data.append('file', fs.createReadStream(image_path));
//Send the file to File-system
console.log("Sending file to S3...");
const axiosResponse = await axios.put(image_signed_url, {
data: data,
headers: { 'Content-Type': 'multipart/form-data' }
}).catch(function(error) {
console.log(JSON.stringify(error));
return null;
});
I have already try to change the headers to {'Content-Type': 'application/octet-stream' } but I obtained the same result.
It did not manage to make AXIOS work in order to upload an image.
The node-fetch module did it sending the image as a binary and specifying the "Content-type".
If I try to the same using AXIOS it the image was always packet into a form-data and the result was JSON file uploaded into the S3 bucket instead of the image.
//Send the file to File-system
console.log("Sending file to S3...");
const resp = await fetch(image_signed_url, {
method: 'PUT',
body: fs.readFileSync(image_path),
headers: {
'Content-Type': 'image/jpeg',
},
}).catch( err => {
console.log(err);
return null;
});
I have a working Apollo-Graphql Node.js server running with express middleware. Queries and Mutations, including file upload mutation, work fine from connecting front end clients and when called from functions run in Node and passed via axios requests -- except for the file upload mutations.
I've tested using the same query and file paths in Firecamp, and tried variations of passing and checking the file path / doing my best to confirm that the directory structure is getting parsed accurately. This is the error code returned with the axios response (I broke up the response for config.data from console output):
response: {
status: 400,
statusText: 'Bad Request',
headers: {
'x-powered-by': 'Express',
'access-control-allow-origin': '*',
'content-type': 'application/json; charset=utf-8',
'content-length': '1424',
etag: 'W/"590-jNRKeEwYD1b3Cxa/bjf3qp7npHg"',
date: 'Fri, 11 Dec 2020 22:36:12 GMT',
connection: 'close'
},
config: {
url: 'http://localhost:4002/graphql',
method: 'post',
data: '{
"query":"mutation singleUpload($file: Upload!) {
singleUpload(file: $file) {
filename
mimetype
encoding
}
}",
"variables":{"file":"../bulkImports/testPenThumbnail.png"}
}',`
The query definition and function call to axios:
const UPLOAD_FILE = gql`
mutation singleUpload($file: Upload!) {
singleUpload(file: $file) {
filename
mimetype
encoding
}
}
`
export function uploadFile(endpoint) {
const file = '../bulkImports/testPenThumbnail.png';
axios.post(endpoint, {
query: print(UPLOAD_FILE),
variables: { file } })
.then(res => console.dir(res.data))
.catch(err => console.error(err));
}
And the resolver for singleUpload
singleUpload(parent, args) {
return args.file.then(file => {
const { createReadStream, filename, mimetype, encoding } = file;
const stream = createReadStream();
const pathName = join(__dirname, `../../testUploads/${filename}`);
stream.pipe(createWriteStream(pathName));
return {
filename,
mimetype,
encoding,
};
});
}
From other errors/debugging along the way, my best guess is that the upload mutation is only seeing the file path as an ordinary String and not parsing it as an Upload scalar -- and that I should be looking at using the fs module to send more in the way of file object data/stream? I've tried a few things using fs methods, but node/back-end is still pretty new to me and I'm not really sure if I'm even barking up the right tree for how the Upload scalar is constructed.
Of course I'm happy to post any more config or error details that would help -- and thanks in advance to everyone who can help me make sense of this or improve the code below!
(oh, and the intended use-case for calling this from a server will be for bulk uploading records to populate a new db collection; besides just trying to learn more about back-end/node/axios/graphql basics...)
I have been working very hard on a Website for my final project in a course at my university. I am having difficulty with understanding why my $.ajax POST request is sending NULL over to my server, when it should ( AND USE TO IN THE PAST) send over the name of my file I have uploaded.
Here's the code for my client side
$('#upload-file').on('change', function (e) {
let uploadname = e.target.files[0].name;
console.log("ICS NAME "+ e.target.files[0].name);
let icsFlag = false;
let formData = new FormData();
formData.append('uploadFile', e.target.files[0].name);
console.log("FORM:" + formData.uploadname);
let fileName = e.target.files[0].name;
if (checkICS(e.target.files[0].name) == true) {
$.ajax({
type: 'POST', //Request type
url: '/upload', //The server endpoint we are connecting to
data: formData,
processData: false,
contentType: false,
success: function (data) {
(the code won't go into either the success or failure section I just get the
400 bad post error)
here is my server-side code (This is the same for everyone in the class to my understanding)
//Respond to POST requests that upload files to uploads/ directory
app.post('/upload', function(req, res) {
console.log("PLEASE"+req.files);
if(!req.files) {
return res.status(400).send('No files were uploaded.');
}
let uploadFile = req.files.uploadFile;
// Use the mv() method to place the file somewhere on your server
uploadFile.mv('uploads/' + uploadFile.name, function(err) {
if(err) {
return res.status(500).send(err);
}
res.redirect('/');
});
});
and here's a picture of my /upload endpoint in the network section of my debug panel ( I was told this may be helpful, something to do with the content-type?)debug-upload-endpoint
You aren't appending the actual file to FormData, only it's name. Then your server side uploader has no file object to work with
Change
formData.append('uploadFile', e.target.files[0].name);
To
formData.append('uploadFile', e.target.files[0]);