How to upload file using fetch - javascript

I know this has been asked before, but none of the solutions are working for me. First I tried to solve this using axios, but reading about it there seem to be a bug that won't allow me to use it for uploading files. So I'm stuck with fetch.
This is my uploading function:
export async function postStudyPlan(plan, headers) {
const options = {
method: "POST",
body: plan
}
return fetch(`${HOST}:${PORT}/study-plans/add`, options)
.then(res => {return res.json()})
.catch(err => console.log(err));
}
This is how I call it:
onStudyPlanUpload(files) {
const file = files[0];
let formData = new FormData();
formData.append("pdf", file);
formData.append("comments", "A really lit study plan!");
formData.append("approved", true);
formData.append("uploaded_by", "Name");
formData.append("date_uploaded", "2012-02-1");
formData.append("university", "australian_national_university");
let plan = {
"pdf": file,
"comments": "A really lit study plan!",
"approved": true,
"uploaded_by": "Name",
"date_uploaded": Date.now(),
"university": "australian_national_university"
}
postStudyPlan(formData)
.then(res => console.log(res))
.catch(err => console.log(err))
}
I know that file is in fact a file. Whenever I change "pdf" to a normal string, everything works fine. But when I use a File object, I recieve nothing to my backend, just an empty object. What am I missing here? I feel like my solution is basically identical to every other solution I've found online.
Edit:
Also tried using FormData and adding headers: {"Content-Type": "application/x-www-form-urlencoded"} to options. Same result.
Edit 2
I'm beginning to think my backend might be the problem! This is my backend, and I'm actually getting some outputs for the "data" event. Not sure how to process it...
router.route("/add").post((req, res) => {
req.on("data", function(data) {
console.log("got data: " + data.length);
console.log("the Data: ?" )
// let t = new Test(data);
// t.save()
// .then(res => console.log(res))
})
req.on("end", function(d) {
console.log("ending!");
})
req.on("error", function(e){
console.log("ERROR: " + e);
})
});

You should use FormData with 'Content-Type': 'application/x-www-form-urlencoded' as fetch header.

I want you to try a simple approach. Instead of appending the file into FormData, create an instance of an actual form.
onStudyPlanUpload = (event) => {
event.preventDefault();
const formData = new FormData(event.target);
postStudyPlan(formData)
.then(res => console.log(res))
.catch(err => console.log(err))
}
HTML
<form onSubmit={this.onStudyPlanUpload} encType="multipart/form-data" ref={el => this.form = el}>
<input type="file" name="pdf" onChange={() => { this.form.dispatch(new Event('submit'))}/>
<input type="hidden" name="comments" value="A really lit study plan!" />
<input type="hidden" name="approved" value=true />
<input type="hidden" name="uploaded_by" value="Name"/>
<input type="hidden" name="date_uploaded" value="2012-02-1"/>
<input type="hidden" name="university" value="australian_national_university"/>
</form>
While changing the file input, It will trigger the form submit (onStudyPlanUpload).
Hope this will work!

I'll answer my own question. If anyone else comes across this problem, it is the BACKEND that's faulty. This is my final solution using busboy to handle incoming form data. I didn't change anything on my server, but my router had to be updated. Here is my router, taking care of POST requests:
const express = require("express");
const mongoose = require("mongoose");
require('./../../models/Test');
const path = require("path");
const fs = require("fs");
const router = express.Router();
const Busboy = require("busboy");
router.route("/add").post((req, res, next) => {
let busboy = new Busboy({headers: req.headers});
// A field was recieved
busboy.on('field', function (fieldname, val, valTruncated, keyTruncated) {
if (req.body.hasOwnProperty(fieldname)) { // Handle arrays
if (Array.isArray(req.body[fieldname])) {
req.body[fieldname].push(val);
} else {
req.body[fieldname] = [req.body[fieldname], val];
}
} else { // Else, add field and value to body
req.body[fieldname] = val;
console.log(req.body);
}
});
// A file was recieved
busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
console.log("File incoming: " + filename);
var saveTo = path.join('.', filename);
console.log('Uploading: ' + saveTo);
file.pipe(fs.createWriteStream(saveTo));
});
// We're done here boys!
busboy.on('finish', function () {
console.log('Upload complete');
res.end("That's all folks!");
});
return req.pipe(busboy);
});
module.exports = router;
Finally, my finished onStydyPlanUpload() function!
onStudyPlanUpload(files) {
const file = files[0];
let formData = new FormData();
formData.append("pdf", file, file.name);
formData.append("comments", "A really lit study plan!");
formData.append("approved", true);
formData.append("uploaded_by", "Melker's mamma");
formData.append("date_uploaded", new Date());
formData.append("university", "australian_national_university");
const HOST = "http://localhost";
const PORT = 4000;
axios.post(`${HOST}:${PORT}/test/add`, formData)
.then(res => console.log(res))
.catch(err => console.log(err))
}
Got help from: https://gist.github.com/shobhitg/5b367f01b6daf46a0287

Related

multer + socket.io + FormData sending file issue

I'm trying to make sending pictures with socket.io and bumped into this error
return req.headers['transfer-encoding'] !== undefined ||
TypeError: Cannot read property 'transfer-encoding' of undefined
Before I used axios + multer, it worked perfect
So there is my code
server:
const fileSchema = new mongoose.Schema({
originalname: String,
filename: String,
path: String,
mimetype: String,
});
const File = mongoose.model("File", fileSchema);
socket.on("ROOM:FILE_UPLOAD", (formData) => {
upload.single("image")(formData, null, (err) => {
if (err) console.log(err);
else {
const originalname = formData.originalname;
const filename = formData.filename;
const path = formData.path;
const mimetype = formData.mimetype;
const file = new File({
originalname,
filename,
path,
mimetype,
});
file.save((err, image) => {
if (err) {
console.error(err);
} else {
console.log(`Image saved to MongoDB: ${image._id}`);
}
});
}
});
});
client:
const [file, setFile] = useState(null);
function handleFileSelect(event: any) {
setFile(event.target.files[0]);
}
function sendMsg(event) {
event.preventDefault();
const formData = new FormData();
formData.append("image", file);
socket.emit("ROOM:FILE_UPLOAD", formData);
}
return (
<div className="container">
<div className="row">
<form encType="multipart/form-data" onSubmit={upload}>
<input
type="file"
accept="image/*"
onChange={handleFileSelect}
name="image"
/>
<button onClick={sendMsg}>send</button>
</form>
</div>
</div>
);
where I made mistake ?
So, it was a bad idea from start to send pictures as message via sockets, it would be big server load, so I would use another algorithm similar to telegram
there is the link on docs. There is no any reason to try fix this error because the main mistake was decision how to send pictures in chat

How to upload Image to Cloudinary - MERN Stack

I want to add some company details to mongo DB, and the details include a company logo. So I want to upload the picture to Cloudinary and then save the URL in Mongo DB with the other details.
But my code doesn't seem to work. When I fill the form and click on submit, the image gets uploaded to Cloudinary but it does not get saved in the Database.
To store the image
const [ companyLogo, setCompanyLogo] = useState("");
const [ companyLogoURL, setCompanyLogoURL] = useState("");
Function to execute on submit
const handleCompanySubmit = (evt) => {
evt.preventDefault();
const data = new FormData()
data.append("file", companyLogo)
data.append("upload_preset", "Sprint")
data.append("cloud_name", "sprint-ccp")
fetch("https://api.cloudinary.com/v1_1/sprint-ccp/image/upload",{
method:"post",
body:data
})
.then(res => res.json())
.then(data => {
setCompanyLogoURL(data.url)
})
.catch(err => {
console.log(err)
})
//check for empty fields
if (
isEmpty(companyName) ||
isEmpty(companyAddress) ||
isEmpty(companyRegNumber) ||
isEmpty(companyContactNumber)
) {
setCompanyErrorMsg("Please Fill All The Fields");
}else {
let formData = new FormData();
formData.append('companyName', companyName);
formData.append('companyAddress', companyAddress);
formData.append('companyRegNumber', companyRegNumber);
formData.append('companyContactNumber', companyContactNumber);
formData.append('companyLogo', companyLogoURL);
setCompanyLoading(true);
addCompany(formData)
.then((response) => {
setCompanyLoading(false);
setCompanySuccessMsg(response.data.successMsg)
setCompanyData({
companyName: "",
companyAddress: "",
companyRegNumber: "",
companyContactNumber: ""
});
})
.catch((err) => {
setCompanyLoading(false);
setCompanyErrorMsg(err.response.data.errorMsg)
})
}
};
const handleCompanyLogo = (evt) => {
setCompanyLogo(evt.target.files[0])
};
frontend view
<form className="register-form" onSubmit={handleCompanySubmit} noValidate>
<label className="text-secondary">Company Logo :</label>
<input type="file" className="form-control" onChange={handleCompanyLogo}/>
//remaining input fields
<button className="btn btn-info submitButton" >Submit</button>
</form>
api for adding company
export const addCompany = async (data) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
const response = await axios.post(
"http://localhost:5000/api/auth/clients/company",
data,
config
);
return response;
};
controller in backend
exports.addNewCompany = async(req,res)=>{
const {
companyName,
companyAddress,
companyRegNumber,
companyContactNumber,
companyLogo
} = req.body;
const company = await Company.findOne({ companyName });
if (company) {
return res.status(400).json({
errorMsg: `${req.body.companyName} already exists`,
});
}
try{
const newCompany = new Company();
newCompany.companyName = companyName;
newCompany.companyAddress = companyAddress;
newCompany.companyRegNumber = companyRegNumber;
newCompany.companyContactNumber = companyContactNumber;
newCompany.companyLogo = companyLogo;
await newCompany.save();
res.json({
successMsg: `${req.body.companyName} Company Added Successfully`
});
} catch (err) {
console.log("clientsController error - Add Company ", err);
res.status(500).json({
errorMsg: "Server Error. Please Try again",
});
}
};
The error i get in the console is this
clientsController error - Add Company Error: Company validation failed: companyLogo: Path companyLogo is required.
at ValidationError.inspect
(C:\CCP\sd08_2021\Backend\node_modules\mongoose\lib\error\validation.js:47:26)
Can you please help me out ?
I think that your error is caused by a more trivial problem :
When you send the POST request with fetch, you don't actually wait for its completion (it's a promise), so the code in the if ... else {...} statement is executed before the termination of the fetch() !
setCompanyLogoURL(data.url) has not been called yet, so formData.append('companyLogo', companyLogoURL); set a blank string instead of the value returned by the call to the Cloudinary API.
The solution would be to make handleCompanySubmit async, and to await for the fetch() promise completion.

Trying to get Information from FormData() , cannot send using front-end

When I send data using postman the Title,description and image I am sending goes through.
This is my post array:
router.post('/', uploadS3.array('meme',3),(req, res, next)=>{
// res.json(req.file.location)
console.log(`req.files =================${req.headers}`);
// console.log("req.file.location===",req.file.location, "req.body===",req.body);
const locationURL = req.files.map(item=>item.location);
//Send image alongside with other form fields
postModel.create({...req.body,image:locationURL}, (error, returnedDocuments) => {
if (error) return next(error);
res.json(returnedDocuments);
})
})
my postman:
When I try to send data using my front end, it does not go to the backend properly, the response I recieve is nothing if I do not include an image, and the title and description is not being passed.... notice that my postModel expects the title and description to be inside of req.body
postModel.create({...req.body,image:locationURL}
I am sending the data by appending it to the FormData() object
Just to be clear, setFiles is the setter for file const [file, setFiles] = useState({ selectedFile: null, loaded: 0 });
const onChangeFile = event => {
if (maxSelectFile(event) && checkMimeType(event) && checkFileSize(event)) {
setFiles({
selectedFile: event.target.files
});
}
console.log(event.target.files)
}
const submitFormHandle = event => {
const data = new FormData();
for (let i = 0; i < file.selectedFile.length; i++) {
data.append("meme", file.selectedFile[i]);
}
data.append("title", title);
data.append("description", description);
axios
.post("http://localhost:3200/api/posts/", data)
.then(res => {
toast.success('upload success')
})
.catch(err => {
toast.error('upload fail')
})
}
You can post axios data by using FormData using below approach :
For the field try using set on FormData object and for image use append like this.
Change your code to:
data.set("title", title);
data.set("description", description);
Kindly let me know if this doesn't work.

How can I send an image from Node.js to React.js?

I have the following setup, by which I send the image, from its url, to be edited and sent back to be uploaded to S3. The problem I currently have is that the image gets on S3 corrupted, and I am wondering if there's trouble in my code that's causing the issue.
Server side:
function convertImage(inputStream) {
return gm(inputStream)
.contrast(-2)
.stream();
}
app.get('/resize/:imgDetails', (req, res, next) => {
let params = req.params.imgDetails.split('&');
let fileName = params[0]; console.log(fileName);
let tileType = params[1]; console.log(tileType);
res.set('Content-Type', 'image/jpeg');
let url = `https://${process.env.Bucket}.s3.amazonaws.com/images/${tileType}/${fileName}`;
convertImage(request.get(url)).pipe(res);
})
Client side:
axios.get('/resize/' + fileName + '&' + tileType)
.then(res => {
/** PUT FILE ON AWS **/
var img = res;
axios.post("/sign_s3_sized", {
fileName : fileName,
tileType : tileType,
ContentType : 'image/jpeg'
})
.then(response => {
var returnData = response.data.data.returnData;
var signedRequest = returnData.signedRequest;
var url = returnData.url;
this.setState({url: url})
// Put the fileType in the headers for the upload
var options = {
headers: {
'Content-Type': 'image/jpeg'
}
};
axios.put(signedRequest,img, options)
.then(result => {
this.setState({success: true});
}).bind(this)
.catch(error => {
console.log("ERROR: " + JSON.stringify(error));
})
})
.catch(error => {
console.log(JSON.stringify(error));
})
})
.catch(error => console.log(error))
Before going any further, I can assure you now that uploading any images via this setup minus the convertImage() works, otherwise the image gets put on S3 corrupted.
Any pointers as to what the issue behind the image being corrupted is?
Is my understanding of streams here lacking perhaps? If so, what should I change?
Thank you!
EDIT 1:
I tried not running the image through the graphicsmagick API at all (request.get(url).pipe(res);) and the image is still corrupted.
EDIT 2:
I gave up at the end and just uploaded the file from Node.js straight to S3; it turned out to be better practice anyway.
So if you are end goal is to upload the image in the S3 bucket using Node Js, there are simple ways by using multer-s3 node module.

How to upload files from ReactJS to Express endpoint

In the application I'm currently working on, there are a couple of file forms that are submitted via superagent to an Express API endpoint. For example, image data is posted like so:
handleSubmit: function(evt) {
var imageData = new FormData();
if ( this.state.image ) {
imageData.append('image', this.state.image);
AwsAPI.uploadImage(imageData, 'user', user.id).then(function(uploadedImage) {
console.log('image uploaded:', uploadedImage);
}).catch(function(err) {
this.setState({ error: err });
}.bind(this));
}
}
and this.state.image is set like this from a file input:
updateImage: function(evt) {
this.setState({
image: evt.target.files[0]
}, function() {
console.log('image:', this.state.image);
});
},
AWSAPI.uploadImage looks like this:
uploadImage: function(imageData, type, id) {
var deferred = when.defer();
request.put(APIUtils.API_ROOT + 'upload/' + type + '/' + id)
.type('form')
.send(imageData)
.end(function(res) {
if ( !res.ok ) {
deferred.reject(res.text);
} else {
deferred.resolve(APIUtils.normalizeResponse(res));
}
});
return deferred.promise;
}
And lastly, the file receiving endpoint looks like this:
exports.upload = function(req, res) {
req.pipe(req.busboy);
req.busboy.on('file', function(fieldname, file) {
console.log('file:', fieldname, file);
res.status(200).send('Got a file!');
});
};
Currently, the receiving endpoint's on('file') function never gets called and so nothing happens. Previously, I've tried similar approaches with multer instead of Busboy with no more success (req.body contained the decoded image file, req.files was empty).
Am I missing something here? What is the best approach to upload files from a (ReactJS) Javascript app to an Express API endpoint?
I think superAgent is setting the wrong content-type of application/x-form-www-encoded instead of multipart/form-data you can fix this by using the attach method like so:
request.put(APIUtils.API_ROOT + 'upload/' + type + '/' + id)
.attach("image-file", this.state.image, this.state.image.name)
.end(function(res){
console.log(res);
});
for more information about the attach method, read the documentation here: http://visionmedia.github.io/superagent/#multipart-requests
since this involves a nodejs server script I decided to make a GitHub repo instead of a fiddle: https://github.com/furqanZafar/reactjs-image-upload
From experience, uploading a file using ajax works when you use FormData, however the file must be the only form field / data. If you try and combine it with other data (like username, password or pretty much anything at all) it does not work. (Possibly there are work arounds to get around that issue, but I am not aware of any)
If you need to send the username/password you should be sending those as headers if you can instead.
Another approach I took was first do the user registration with the normal data, then on success I upload the file with the FormData separately as an update.
The react file upload iamges component:
class ImageUpload extends React.Component {
constructor(props) {
super(props);
this.state = {file: '',imagePreviewUrl: ''};
}
_handleSubmit(e) {
e.preventDefault();
// this.uploadImage()
// TODO: do something with -> this.state.file
console.log('handle uploading-', this.state.file); }
_handleImageChange(e) {
e.preventDefault();
let reader = new FileReader();
let file = e.target.files[0];
reader.onloadend = () => {
this.setState({
file: file,
imagePreviewUrl: reader.result
});
}
reader.readAsDataURL(file) }
// XHR/Ajax file upload uploadImage(imageFile) {
return new Promise((resolve, reject) => {
let imageFormData = new FormData();
imageFormData.append('imageFile', imageFile);
var xhr = new XMLHttpRequest();
xhr.open('post', '/upload', true);
xhr.onload = function () {
if (this.status == 200) {
resolve(this.response);
} else {
reject(this.statusText);
}
};
xhr.send(imageFormData);
}); }
render() {
let {imagePreviewUrl} = this.state;
let $imagePreview = null;
if (imagePreviewUrl) {
$imagePreview = (<img src={imagePreviewUrl} />);
} else {
$imagePreview = (<div className="previewText">Please select an Image for Preview</div>);
}
return (
<div className="previewComponent">
<form onSubmit={(e)=>this._handleSubmit(e)}>
<input className="fileInput" type="file" onChange={(e)=>this._handleImageChange(e)} />
<button className="submitButton" type="submit" onClick={(e)=>this._handleSubmit(e)}>Upload Image</button>
</form>
<div className="imgPreview">
{$imagePreview}
</div>
</div>
) } } React.render(<ImageUpload/>, document.getElementById("mainApp"));
The Server Side Image Save and Copy:
Along with express You needed to npm install 'multiparty'. This example uses multiparty to parse the form data and extract the image file information. Then 'fs' to copy the temporarily upload image to a more permanent location.
let multiparty = require('multiparty');
let fs = require('fs');
function saveImage(req, res) {
let form = new multiparty.Form();
form.parse(req, (err, fields, files) => {
let {path: tempPath, originalFilename} = files.imageFile[0];
let newPath = "./images/" + originalFilename;
fs.readFile(tempPath, (err, data) => {
// make copy of image to new location
fs.writeFile(newPath, data, (err) => {
// delete temp image
fs.unlink(tempPath, () => {
res.send("File uploaded to: " + newPath);
});
});
});
})
}

Categories