How to upload files from ReactJS to Express endpoint

All we need is an easy explanation of the problem, so here it is.

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?

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

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

Method 2

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.

Method 3

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

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply