Reputation: 391
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.....
Upvotes: 1
Views: 1881
Reputation: 164980
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 following
const 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.
Upvotes: 2