Image uploads in react native not reaching server - node.js

I am sending images to a nodeJs server using react native. I have noticed that when i make the request, there is an image file in the request but the response fails saying photos are needed for file upload. The request however works perfectly on postman.
Here is a sample of the code. I am using react native image crop picker to select the image.
const choosePhotoFromLibrary = () => {
ImagePicker.openPicker({
width: 300,
height: 400,
cropping: true,
// multiple: true,
multiple: false,
mediaType: 'photo'
}).then((image) => {
setPhotos(image.path)
}).catch(err => {
console.log(err);
})
}
getStoreId()
const createProduct = async () => {
console.log(typeof photos);
const data = new FormData()
data.append('name', productName)
data.append('description', description)
data.append('price', price)
data.append('category', productCategory)
data.append('sub-category', productSubCategory)
data.append('condition', productCondition)
data.append('photos', photos)
data.append('type', `Women's wear`)
console.log(data);
var config = {
method: 'post',
url:url,
headers: {
'token': token,
'Content-Type': 'multipart/form-data'
},
data: data
};
try {
const product = await axios(config)
console.log(product);
} catch (err) {
console.log(err.response);
}

Instead of
data.append('photos', photos)
write like this
data.append('photos', {
name: "Example.jpg",
uri: photos, // This should be something like 'file://...'
type: "image/jpg" // MIME type of file
})

Related

React Native Axios cannot upload files to server FormData

Im implementing a function to upload titles, descriptions, and images to the server. Here is my snippet of Front-end code:
const [imageFiles, setImageFiles] = React.useState([]);
const callApi = async () => {
const formData = new FormData();
formData.append('title', 'test');
formData.append('content', 'tesst1');
imageFiles.forEach(imageFile => {
formData.append('files', imageFile);
});
const response = await axios({
method: 'post',
url: 'http://localhost:8080/api/complain',
headers: {
'Content-Type': 'multipart/form-data; ',
Authorization:
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
},
data: formData,
});
};
const openCamera = () => {
let imgList = [];
ImagePicker.openPicker({
multiple: true,
waitAnimationEnd: false,
includeExif: true,
compressImageQuality: 0.8,
maxFiles: 5,
includeBase64: true,
})
.then(images => {
images.map(image => {
imgList.push({
filename: image.filename,
path: image.path,
data: image.data,
type: image.mime,
height: image.height,
width: image.width,
});
});
setImageFiles(imgList);
})
.catch(err => {
console.log(err);
});
};
Here is the Back end code:
router.post(
"/complain",
authenticateToken,
upload.array("files"),
complainController.createComplain
);
However, when I console log req.files, it returns an empty array. I have tried different methods such as add transform request, using fetch from react native,... but none of them works. Please help me with this issue. Thank you guys.

Axios POST request sending nothing with 'multipart/form-data' [React Native - Expo]

Scenario
Front end is basically a React Native (Expo) application where users can issue a report - this includes taking multiple photos and filling in some details.
Backend is just node.js, with Express & Multer.
Problem
I use Axios to send the images and form data through FormData(), however on the server side, req.body and req.files consist of nothing.
One thing here is that sending the SAME data through POSTMAN works COMPLETELY fine, the images were stored into S3 and the form details were stored in the database. It is through the app/emulator that does not work.
I've tried removing the "multipart/form-data" header and this is the output from console.log(req.body) (req.files show undefined):
{
_parts: [
[ 'userId', '1f400069-07d0-4277-a875-cbb2807750c5' ],
[
'location',
'some location'
],
[ 'description', 'Aaaaa' ],
[ 'report-images', [Object] ]
]
}
When I put the "multipart/form-data" header back this output didn't even bother showing up.
What I've Done
I've been searching for solutions for the past hours and none of them worked. Those solutions are:
Adding boundary behind the "multipart/form-data" header
Putting the type to "image/jpeg"
Trimming the file uri to "file://"
Yet none of them works
Here is my code:
React Native Frontend (Expo)
const submitReport = async () => {
setShowLoading(true);
// Validate details (location & images)
if (location === "") {
setShowLoading(false);
showToast(7002);
return;
}
if (images.length === 0) {
setShowLoading(false);
showToast(7004);
return;
}
try {
const formData = new FormData();
formData.append("userId", user.userId);
formData.append("location", location);
formData.append("description", description);
images.forEach(img => {
const trimmedURI = (Platform.OS === "android") ? img.uri : img.uri.replace("file://", "");
const fileName = trimmedURI.split("/").pop();
const media = {
name: fileName,
height: img.height,
width: img.width,
type: mime.getType(trimmedURI),
uri: trimmedURI
};
formData.append("report-images", media);
});
const response = await axios.post(`http://<my-ip-address>:3003/api/report/submit`, formData, { headers: { 'Content-Type': "application/x-www-form-urlencoded" } });
console.log(response)
setShowLoading(false);
}
catch (error) {
console.log(error);
setShowLoading(false);
showToast(9999);
}
};
Backend
// Multer-S3 Configuration
const upload = multer({
storage: multerS3({
s3: s3,
bucket: process.env.AWS_S3_BUCKET_NAME,
contentType: (req, file, callback) => {
callback(null, file.mimetype);
},
metadata: (req, file, callback) => {
callback(null, { fieldName: file.fieldname });
},
key: (req, file, callback) => {
callback(null, `${process.env.AWS_S3_REPORT_IMAGES_OBJECT_PATH}${req.body.userId}/${new Date().getTime().toString()}-${file.originalname}`)
}
}),
fileFilter: (req, file, callback) => {
// Check if file formats are valid
if (file.mimetype === "image/png" || file.mimetype === "image/jpg" || file.mimetype === "image/jpeg") {
callback(null, true);
}
else {
callback(null, false);
return callback(new Error("Image File Type Unsupported"));
}
},
});
router.post("/submit", upload.array("report-images", 3), async (req, res) => {
try {
// req -> FormData consisting of userId, location & description
// multer-s3 library will handle the uploading to S3 - no need to code here
// Details of files uploaded on S3 (Bucket, Key, etc.) will be displayed in req.files
// Analyze from Rekognition
//Add to Database code blablabla
if (result.success === true) {
res.status(200).send({ message: result.data });
}
else if (result.success === false) {
res.status(404).send({ error: ERROR_MESSAGE });
}
}
catch (error) {
console.log(error);
res.status(404).send({ error: ERROR_MESSAGE });
}
});
I'm unsure if this an Axios problem or some problem on my side.
This project is for my Final Year Project.
So after diving through search results in Google, I've found this StackOverflow post: react native post form data with object and file in it using axios
I took the answer provided by user_2738046 in my code and it worked! Combining with Ali's suggestion here is the final code that worked.
const FormData = global.FormData;
const formData = new FormData();
formData.append("userId", user.userId);
formData.append("location", location);
formData.append("description", description);
images.forEach(img => {
const trimmedURI = (Platform.OS === "android") ? img.uri : img.uri.replace("file://", "");
const fileName = trimmedURI.split("/").pop();
const media = {
name: fileName,
height: img.height,
width: img.width,
type: mime.getType(trimmedURI),
uri: trimmedURI
};
formData.append("report-images", media);
});
const response = await axios({
method: "POST",
url: `http://${<my-ip-address>}:3003/api/report/submit`,
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
},
transformRequest: (data, error) => {
return formData;
}
});
// If success, clear all text fields
if (response) {
showToast(7005);
setLocation("");
setImages([]);
setDescription("");
}
setShowLoading(false);
You need to change your image uploading code with this one, you also need to install mime npm package.
const formData = new FormData();
formData.append("userId", user.userId);
formData.append("location", location);
formData.append("description", description);
const formData = new FormData();
files = files || [];
if (files.length) {
for (let index = 0; index < files.length; index++) {
const filePayload = files[index];
const file = filePayload.value;
const localUri =
Platform.OS === "android" ?
file.uri :
file.uri.replace("file://", "");
const newImageUri = localUri;
const filename = newImageUri.split("/").pop();
const media = {
name: filename,
height: file?.height,
width: file?.width,
type: mime.getType(newImageUri),
uri: localUri,
};
formData.append(filePayload.name, media);
}
}
const response = await axios.post(`http://<my-ip-address>:3003/api/report/submit`, formData, {
headers: headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});

Cannot make a request of image with expo-camera takePictureAsync method to node.js backend server

I am struggling from taking pictures with expo camera on react native and sending the cache image to my node.js backend server and then my backend server appends this to a formdata object and sends it to my webservice. I searched a lot about the operations between my frontend and backend but couldn't find the exact true answer.
My express-node backend server getting images with multer.
I have a react native frontend code like below in order to send my image data I got as returned object of takePictureAsync method of expo-camera:
CLIENT SIDE
//react native client side
const takePicture = async () => {
if (cameraRef.current) {
const options = { quality: 0.5, base64: true, skipProcessing: true };
const data = await cameraRef.current.takePictureAsync(options);
const source = data.uri;
if (source) {
await cameraRef.current.pausePreview();
setIsPreview(true);
uploadFile(source);
console.log('picture source', source);
}
}
};
Then I get 404 status error from my backend when I try to send this image data like below with axios to my node.js backend server:
//react native client side
async function uploadFile(photo) {
const formData = new FormData();
formData.append('file', {
uri: photo,
name: 'test',
mimetype: 'image/jpeg',
});
await axios
.post('http://MyLocalIpAdress:3000/photo-upload', formData, {
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
},
})
.then((res) => {
console.log(res.data);
return res.data;
});
}
SERVER SIDE
My Node.js backend endpoint is as below:
router.post(
'/photo-upload',
multer({ storage: multer.memoryStorage() }).single('file'),
async (req, res) => {
if (req.file) {
try {
// Transfers uploaded image through webservice
const form = new FormData();
form.append('file', req.file.buffer, {
contentType: req.file.mimetype,
filename: req.file.originalname,
});
res.status(200).send({
message: 'Success'
});
} catch (err) {
res.status(500).send({
message: `Could not upload the file: ${req.file.originalname}. ${err}`,
});
}
} else {
return res.status(400).send({ message: 'Please upload a file!' });
}
})
I couldn't figure out whether I'm doing things wrong on server side or client side
and the way of doing it.
I faced same issue with sending image data to backend using formData. There are a couple of tricks to solve this:
Solution 1:
const formdata = new FormData();
formdata.append('image[]', {
name: 'test',
type: imageurl?.type,
uri:
Platform.OS !== 'android'
? 'file://' + photo
: photo,
});
const res = await axios.post('http://MyLocalIpAdress:3000/photo-upload', formdata, {
headers: {
Accept: '*/*',
'Content-type': 'multipart/form-data',
},
});
Solution 2: (My personal choice) is to use a library to upload the data. rn-fetch-blob is something that I have used to solve this. If you plan to use this, go through the documentation and implement it.
RNFetchBlob.fetch('POST', 'http://MyLocalIpAdress:3000/photo-upload',
{
Authorization : "Bearer access-token",
'Content-Type' : 'multipart/form-data',
}, [
// element with property `filename` will be transformed into `file` in form data
{ name : 'avatar', filename : 'avatar.png', data: binaryDataInBase64},
// custom content type
{ name : 'avatar-png', filename : 'avatar-png.png', type:'image/png', data: binaryDataInBase64},
// part file from storage
{ name : 'avatar-foo', filename : 'avatar-foo.png', type:'image/foo', data: RNFetchBlob.wrap(path_to_a_file)},
// elements without property `filename` will be sent as plain text
{ name : 'name', data : 'user'},
{ name : 'info', data : JSON.stringify({
mail : 'example#example.com',
tel : '12345678'
})},
]).then((resp) => {
// ...
}).catch((err) => {
// ...
})

PDF not uploading to Amazon S3 with Nodejs and Reactjs

I am working on uploading PDF files to S3 using nodejs and react and I am running into an issue where some PDFs are being uploaded and some are not.
This endpoint gets a signed url from AWS
'/api/v1/upload/pdf',
requireAuth,
roleAuthorization(['admin']),
(req, res) => {
const date = new Date();
const year = date.getFullYear();
const key = `${date.toLocaleString('default', {
month: 'long',
})}-${short.uuid('0123456789').slice(0, 2)}-${year}.pdf`;
s3.getSignedUrl(
'putObject',
{
Bucket: 'bucket-name',
ContentType: 'application/pdf',
Key: key,
},
(err, url) => res.send({ key, url })
);
}
);
And this endpoint does the upload from react js and save the link to the file in the database
const createIssue = (dispatch) => async (issue, town) => {
try {
const token = await localStorage.getItem('token');
const uploadConfig = await axios.get('/api/v1/upload/pdf', {
headers: {
Authorization: `Bearer ${token}`,
},
});
const upload = await axios.put(uploadConfig.data.url, issue, {
headers: {
'Content-Type': issue.type,
},
});
const response = await api.post(`/api/v1/town/${town._id}/issue`, {
issue: uploadConfig.data.key,
});
dispatch({ type: 'create_issue', payload: response.data });
} catch (err) {
dispatch({
type: 'add_error',
payload: err.message,
});
}
};
This works but does not work for all PDF files, the file remains pending and does not upload.
Any help welcome.
thanks

what is the best way to upload photo from phone to node server

i wondering what is the best way to upload photo from my phone(using react native) to my node server.
currently I encode my picture in base64 and store it in a LONGTEXT
but is there a more efficient way to do it ?
i'm using
'Content-Type': 'application/x-www-form-urlencoded'
to reach my API
thanks
Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files.
It is very simple just copy paste the code in documentation.
I would recommend using formdata instead of base64.
For speed and effeciency, maybe consider resizing your image first before transit and perharps creating a thumbnail image for client's side browsing.
This example Im using Axios, 'react-native-image-picker', 'react-native-image-resizer' and Redux
Api.js
export const api = axios.create({
baseURL: server,
headers: {
'Cache-Control': 'no-cache'
},
timeout: 5000
})
PhotoUpload.js
uploadPicture = (photo) => {
api.post('/image/'+this.state.position, photo)
.then(() => {
this.props.getThumbList()
.then((response) => {
this.props.setThumbSource(response.payload.data)
this.setState({thumbUri: {uri: this.props.thumbSource[this.state.position]}})
})
.catch((error) => {
console.log(this.props.errorText)
})
})
.catch((error) => {
console.log(this.props.errorText)
})
}
openImagePicker = () => {
// get image from image picker
ImagePicker.showImagePicker(this.options, async response => {
this.setState({
originUri: response.uri
})
if (response.didCancel) {
console.log('User cancelled image picker')
return
} else if (response.error) {
console.log('ImagePicker Error: ', response.error)
return
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton)
return
}
//Post the photo with position which it was selected
const photo = new FormData();
// data.append('authToken', 'secret');
photo.append('photo', {
uri: this.state.originUri,
type: 'image/jpeg',
name: 'p-'+this.state.position+'.jpg'
});
let { height, width, quality, format, originUri } = this.state
// Resize and post the thumb
const resizedImageUri = await ImageResizer.createResizedImage(
originUri,
height,
width,
format,
quality
).then(({uri}) => {
photo.append('thumb', {
uri: uri,
type: 'image/jpeg',
name: 't-'+this.state.position+'.jpg'
});
this.uploadPicture(photo);
})
})
}
Redux.js
export const GET_THUMB_LIST = 'GET_THUMB_LIST';
export const GET_THUMB_LIST_SUCCESS = 'GET_THUMB_LIST_SUCCESS';
export const GET_THUMB_LIST_FAIL = 'GET_THUMB_LIST_FAIL';
export const SET_THUMB_SOURCE = 'SET_THUMB_SOURCE';
export const SET_THUMB_SOURCE_FAIL = 'SET_THUMB_SOURCE_FAIL';
export function getThumbList() {
return {
type: GET_THUMB_LIST,
payload: {
request: {
method: 'GET',
url:'/thumbs'
}
}
};
}
export function setThumbSource(list) {
return {
type: SET_THUMB_SOURCE,
payload: list
};
}
export default function reducer(state = {}, action) {
switch (action.type) {
case GET_THUMB_LIST_SUCCESS:
// console.log(action.payload.data)
return {
...state,
thumbList: action.payload.data
}
case GET_THUMB_LIST_FAIL:
return {
...state,
errorText: "Cannot get image list"
}
case SET_THUMB_SOURCE:
return {
...state,
thumbSource: action.payload
}
case SET_THUMB_SOURCE_FAIL:
return {
...state,
errorText: "Set thumb uri failed"
}
default:
return state
}
}

Resources