1

I am trying to send a file from a react web app to node.js server.

I am starting off with an html input of type file, Once user uploads their file, I trigger a post request to my Node.js server.

Once in my Node.js server, I need to send a post request to a 3rd party API (Twilio)

3rd party API needs my file as binary data.

Note: from docs "The body or content of the POST must be the file itself in binary format."

Here is React component below

import React, { Fragment, useState, useRef } from "react";
import { Col, Button, Media, Card, CardBody } from "reactstrap";
import { useSelector, useDispatch } from "react-redux";
import { attachMMS } from "../../../actions/index";
import cloudUpload from "../../../assets/img/icons/cloud-upload.svg";

const InsertMMS = ({
  showFileModal,
  setShowFileModal,
  showInsertMMSModal,
  setShowInsertMMSModal,
  uploadMMS,
  setUploadMMS,
}) => {
  const InputFile = useRef(null);
  const [userMMS, setUserMMS] = useState();
  const [highlighted, setHighlighted] = useState(false);
  const dispatch = useDispatch();

  const attachMMSCreate = useSelector((state) => state.attachMMSCreate);

  const manualMMSUpload = (e) => {
    console.log("manualMMSUpload ran");
    e.preventDefault();
    if (e.target.files[0].name) {
      console.log("file", e.target.files[0]);
      getBinary(e.target.files[0]);
      setUserMMS(e.target.files[0].name);
    }
  };

  const getBinary = (file) => {
    const reader = new FileReader();
    reader.readAsArrayBuffer(file);
    reader.onload = () => {
      setUploadMMS({ file: file, fileType: file.type });
    };
  };

  const handleInputChange = ({ value, name }) =>
    setUploadMMS({ ...uploadMMS, [name]: value });

  const onButtonClick = (e) => {
    e.preventDefault();
    InputFile.current.click();
  };

  return (
    <Fragment>
      <Card>
        <CardBody className="fs--1 font-weight-normal p-4">
          <h4 className="mb-1 mt-3 text-center"></h4>
          <div>
            <input
              type="file"
              onChange={(e) => manualMMSUpload(e)}
              accept=".gif, .png, .jpeg"
              ref={InputFile}
              className="d-none"
            />
            <div
              className={`mb-2 p-3 border-dashed border-2x border-300 bg-light rounded-soft text-center cursor-pointer ${
                highlighted ? " border-800" : ""
              }`}
              onClick={(e) => onButtonClick(e)}
              onDragEnter={() => {
                setHighlighted(true);
              }}
              onDragLeave={() => {
                setHighlighted(false);
              }}
              onDragOver={(e) => {
                e.preventDefault();
              }}
              onDrop={(e) => {
                e.preventDefault();
              }}
            >
              <Fragment>
                <Media className=" fs-0 mx-auto d-inline-flex align-items-center">
                  <img src={cloudUpload} alt="" width={25} className="mr-2" />
                  <Media>
                    <p className="fs-0 mb-0 text-700">
                      {userMMS ? userMMS : "Upload your File"}
                    </p>
                  </Media>
                </Media>
                <p className="mb-0 w-75 mx-auto text-500">
                  Supports: .gif, .png, .jpeg{" "}
                </p>
              </Fragment>
            </div>
          </div>
          <p className="fs-0 text-center">multi-media message</p>
          <Col className="text-center">
            <Button
              disabled={!uploadMMS}
              color="primary"
              onClick={() => {
                return (
                  dispatch(attachMMS(uploadMMS)),
                  setShowInsertMMSModal(!showInsertMMSModal),
                  setShowFileModal(!showFileModal)
                );
              }}
              className="my-3 text-white"
            >
              {attachMMSCreate.loading ? "...processing" : "Attach MMS"}
            </Button>
          </Col>
        </CardBody>
      </Card>
    </Fragment>
  );
};

export default InsertMMS;

Here is the Action being called from React making the POST request to Node.js server

export const attachMMS = (uploadMMS) => async (dispatch) => {
  console.log("body of attachMMS in actions", uploadMMS);
  try {
    dispatch({ type: ATTACH_MMS_CREATE_REQUEST });
    await axios({
      url: "http://localhost:5000/mms",
      method: "POST",
      data: uploadMMS.file,
      withCredentials: true,
    }).then((res) =>
      dispatch({ type: ATTACH_MMS_CREATE_SUCCESS, payload: res })
    );
  } catch (error) {
    console.log(error);
    dispatch({
      type: ATTACH_MMS_CREATE_FAIL,
      payload:
        error.message && error.response.data.message
          ? error.response.data.message
          : error.message,
    });
  }
};

Here is the Node.js route being triggered

module.exports = (router) => {
  router.route("/mms").post(twilCtrl.createMediaMMSResource);
};

Here is the Node.js function being called

const createMediaMMSResource = async (req, res) => {
  try {
    const { subActServiceSid, subActAuthToken, convoServiceSid } = req.user;
    let mediaResource;

    await axios({
      url: `https://mcs.us1.twilio.com/v1/Services/${convoServiceSid}/Media`,
      method: "POST",
      data: req.body, // this needs to be the file itself in binary format
      headers: {
        "Content-Type": "image/png",
      },
      auth: { username: subActServiceSid, password: subActAuthToken },
      withCredentials: true,
    }).then((res) => {
      console.log(
        "res from first post request to twilio with fileData",
        res.data
      );
      return (mediaResource = {
        mediaUrl: res.data.url,
        serviceSid: res.data.service_sid,
        mediaSid: res.data.sid,
      });
    });
    res.status(201).json(mediaResource);
  } catch (err) {
    console.log(err);
  }
};

Here is the response I get from client....

Does anyone see the issue? I cant seem to understand how to send "Binary" data to node.js server.....

enter image description here

Phil
  • 157,677
  • 23
  • 242
  • 245
KingJoeffrey
  • 303
  • 5
  • 16
  • In JavaScript, binary data is typically represented using [typed arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays) (most commonly a [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), in which each array element is equal to a single byte). – jsejcksn Jul 06 '22 at 01:47
  • 1
    i think you'll have to send the whole file as form-data and use multer middleware to extract the file from req body. And don't forget to send **content-type:multipart/form-data** when you send a POST request or whatever HTTP method you're using – callmeizaz Jul 06 '22 at 02:30

1 Answers1

2

It is possible to send binary data from the browser which you can handle in Express via the express.raw() middleware however I would do the following instead...

  1. Upload the file to your Express service via FormData. This is typically how browsers perform binary uploads and utilities like Axios make it even easier.

    const manualMMSUpload = (e) => {
      if (e.target.files.length > 0) {
        const file = e.target.files[0];
        setUploadMMS({ file, fileType: file.type });
        setUserMMS(file.name);
      }
    };
    
    // export const attachMMS = (uploadMMS) => async (dispatch) => {
    // ...
    
    // use axios.postForm()
    const res = await axios.postForm("http://localhost:5000/mms", {
      file: uploadMMS.file
    }, { withCredentials: true }); // do you actually need cookies?
    dispatch({type: ATTACH_MMS_CREATE_SUCCESS, payload: res})
    
    // ...
    

    FYI you do not need withCredentials unless you're relying on cookies.

    If you're on an older version of Axios without postForm, use the following

    const data = new FormData();
    data.append("file", uploadMMS.file);
    const res = await axios.post("http://localhost:5000/mms", data);
    
  2. Handle the file upload in your Express service using the Multer or express-fileupload middleware.

    const fileUpload = require('express-fileupload');
    router.route('/mms').post(fileUpload(), twilCtrl.createMediaMMSResource)
    
  3. Pass the uploaded file buffer through to the upstream API.

    const response = await axios.post(
      `https://mcs.us1.twilio.com/v1/Services/${convoServiceSid}/Media`,
      req.files.file.data, // here's the buffer
      {
        auth: { username: subActServiceSid, password: subActAuthToken },
        headers: {
          "content-type": req.files.file.mimetype,
        },
      }
    );
    

    This should upload the file data buffer as raw binary data.

Phil
  • 157,677
  • 23
  • 242
  • 245
  • Phil, Thanks so much. I was missing the middleware (multer or express-fileupload) I used express-fileupload. your solution works as long as you have axios version "0.27.0" or later. I have axios "0.21.0" on my machine. I will elaborate on your answer that worked for me. Thank you so much this should be part of twilio docs when sending MMS via conversations api. @philnash – KingJoeffrey Jul 06 '22 at 02:39
  • @ErikaPeterson007 any reason you can't use latest Axios? I'll update my answer to show how to use earlier versions – Phil Jul 06 '22 at 02:40
  • its that axios.postForm, in my version I got axios.postForm is not a function. Just had to manually make formData in client and add "content-type: multipart/form-data" header when making axios post – KingJoeffrey Jul 06 '22 at 02:45
  • 1
    @ErikaPeterson007 [You don't need to add content-type](https://stackoverflow.com/a/68643919/283366). See my updated answer – Phil Jul 06 '22 at 02:46
  • your right, no need for content-type, I just needed to manually make the formData in client – KingJoeffrey Jul 06 '22 at 02:49