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.....
CodePudding user response:
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...
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 followingconst data = new FormData(); data.append("file", uploadMMS.file); const res = await axios.post("http://localhost:5000/mms", data);
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)
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.