In this tutorial, we discuss how to upload binary files using FormData and XMLHttpRequest, and parsing multipart/form-data requests server side using Multer. This method is one of several other possible methods to upload binary data.
In our code, once a user choses an image in AddRecipe, an UPLOAD_REQUEST action will be dispatched, and handled by AddRecipePageSagas.js as follows:
function* uploadRequest(action) {
  console.log('uploadRequest=', action);
  const channel = yield call(_createUploadFileChannel, '/api/upload/file', action.payload);
  const result = yield take(channel);
  if (result.success)
    action.payload.imageURL = result.response.url.replace(/\\/g,"/");
    yield put({type: action.type_success, payload: {...action}});
  if (result.err)
    yield put({type: action.type_failure, payload: {file: action.payload, err: result.err}});
}
The saga calls _createUploadFileChannel function which returns a channel created specifically for the received event, then it will retrieve the response from it once it done sending the data and receives a response.
If the result is successful, it will dispatch a sucess action, otherwise a failure action.
function _createUploadFileChannel(endpoint, file) {
  return eventChannel(emitter => {
      const xhr = new XMLHttpRequest();
      xhr.responseType = "json";
      const onFailure = (e) => {
        emitter({err: new Error('Upload failed')});
        emitter(END);
      };
      xhr.upload.addEventListener("error", onFailure);
      xhr.upload.addEventListener("abort", onFailure);
      xhr.onreadystatechange = () => {
        const {readyState, status} = xhr;
        if (readyState === 4) {
          if (status === 200) {
            emitter({success: true, response: xhr.response});
            emitter(END);
          } else {
            onFailure(null);
          }
        }
      };
      xhr.open("POST", endpoint, true);
      let formData = new FormData();
      formData.append('filename', file.filename);
      formData.append('buffer', file.data);
      xhr.send(formData);
      return () => {
        xhr.upload.removeEventListener("error", onFailure);
        xhr.upload.removeEventListener("abort", onFailure);
        xhr.onreadystatechange = null;
        xhr.abort();
      };
    },
    buffers.sliding(2));
}
This helper function returns an arrow function as a result of its execusion. Its main purpose is to create a configured instance following the received parameters as follows:
function* AddRecipePageSagas() {
  yield takeEvery(uploadConstants.UPLOAD_REQUEST, uploadRequest);
}
let Recipe = require(MODEL_PATH + 'Recipe');
let upload = require('multer')();
let base64Img = require('base64-img');
let generateSafeId = require('generate-safe-id');
module.exports = (app) => {
    .
    .
    .   
  app.post('/api/upload/file', upload.any(), function (req, res, next) {
    console.log("app.post('/api/upload/file')");
    let baseURL = 'http://132.72.46.150:8080/';
    let filename = generateSafeId();
    let relativePath = "server/public/";
    let absfilepath = base64Img.imgSync(req.body.buffer, relativePath + 'images/', filename);
    let correctPath = absfilepath.substr(relativePath.length, absfilepath.length);
    //let img_url = new URL(correctPath, baseURL);
    let img_url = baseURL + correctPath;
    res.json({url: img_url});
    res.end();
  });
    .
    .
    .
}
The flow at server is as follows:
This tutorial provided the basic details of the following elements:
Created: August 5th, 2018 Updated: August 5th, 2018