3

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?

3
  • did u try socket.io? it can be used to transmit binary data and jsons Commented Nov 20, 2014 at 17:51
  • @DmitryMatveev I think socket.io would be a little overkill just for sending some JSON/files from client to server unfortunately. I don't really need all the real-time features Commented Nov 20, 2014 at 18:00
  • What frontend are you using. If not so specific, you can give it a try for jQuery Form plugin. It emits necessary events like beforeSubmit, uploadProgress, success, error. Server side, you can use Node Formidable. Commented Dec 5, 2014 at 7:36

3 Answers 3

7
+50

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

Sign up to request clarification or add additional context in comments.

Comments

1

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.

4 Comments

That's actually the approach I initially took. the JSON is sent and processed just fine, but once I get to the image upload step, req.files is still empty (the file is actually inside req.body as a bunch of unicode).
I meant from the client side...req.files sounds like a client side entity
I am uploading them via ajax from the client side, but they're being sent to the Express endpoint which is where req.body comes into play.
In that case you may be affected by this - stackoverflow.com/questions/23114374/…
0

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.