multer, react-native image upload not working - node.js

I am trying to upload a picture with an image picker. I made an image component and make an image-picker fill the component, but uploading the image data to the server doesn't work.
this is the code that is called by onPress attached to a button:
const handlePhoto = async () =>{
if(image!== null){
console.log(image)
const fileToUpload = image;
const data = new FormData();
data.append('name', 'Image Upload');
data.append('file', fileToUpload);
let res = await fetch(
'http://localhost:3000/user/pic_upload',
{
method: 'post',
body: data,
headers: {
'Content-Type': 'multipart/form-data; ',
},
}
);
let responseJson = await res.json();
console.log(responseJson,'result')
if (responseJson.status == 1) {
alert('Upload Successful');
}
} else {
alert('Please Select File first');
}
}
and at index file for user router:
const upload=multer({
storage:multer.diskStorage({
destination:function(req,file,callback){
callback(null,'uploads/')
},
filename:function(req,file,callback){
callback(null,new Date().valueOf()+path.extname(file.originalname))
}
}),
});
router.post('/pic_upload', upload.single('file'), controller.picUpload)
and picUpload router just has console.log
What should I fix to make it work?

Related

How can I upload an image and form data as well on a single button click?

I am making a practice project in MERN stack and wanted to upload images from react js with the form on single button click, so I needed to call two apis on just one button click. But I am having errors that's why I am unable to upload image and form data as well.
My react js code here:
const URL = "http://localhost:2040/add_recipe";
const formData = new FormData();
formData.append("recipe_image", selectedFile);
let config = {
headers: {
"Content-Type": "multipart/form-data",
authorization: JSON.parse(localStorage.getItem("token")),
},
};
axios
.post(URL, formData, config)
.then((response) => {
console.log("Image uploaded successfully" + response);
})
.catch((error) => {
console.log("Error while uploading image" + error);
});
and here is my backend api:
const Recipe = require("../models/Recipe");
const fs = require("fs");
let filePath = "";
const AddRecipe = async (req, res) => {
if (req.file) {
filePath = req.file.path;
}
console.log("filepath: " + filePath);
let recipee = await Recipe.findOne({ name: req.body.name });
if (recipee) {
res.json({ Response: "Recipe already exists!" });
} else {
if (
req.body.category &&
req.body.ptime &&
req.body.name &&
req.body.noOfPeople &&
req.body.shortDesc &&
req.body.recipe
) {
let recipe = await Recipe.create({
category: req.body.category,
ptime: req.body.ptime,
name: req.body.name,
noOfPeople: req.body.noOfPeople,
shortDesc: req.body.shortDesc,
recipe: req.body.recipe,
avatar: {
data: fs.readFileSync(filePath),
contentType: "image/png",
},
});
let result = await recipe;
console.log(filePath + " .......path");
if (result.name) {
res.json({ Response: "Recipe added successfully!" });
} else {
res.json({ Response: "Recipe not added!" });
}
}
}
};
module.exports = { AddRecipe };
This is how I called the api with multer already setup
app.post("/add_recipe", verifyToken, upload.single("recipe_image"), AddRecipe);
I found the answer, actually I had to sent all data using FormData inside the axios request and its content-type would be multipart/form-data.
So, the request should be one because url is same and we can send form data and image as well using FormData append method and on backend we can get image as req.file and data as req.body.*
That's all!

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",
},
});

Vue - How to display image received from backend API?

I'm building a webApp in MEVN stack (Mongo, Express, Vue, Node).
In my backend, I have a controller (./backend/controllers/screenshots.controller.js) downloading an image from an external REST API. The image (PNG) is downloaded in a directory called 'images' placed in the controllers directory.
screenshots.controller.js:
const path = require('path');
const axios = require('axios');
const fs = require('fs');
const downloadScreenshot = async(screenshotPath) => {
let isDownloaded = false;
const fileUrl = `https://myexternalapi.com/screenshot/${screenshotPath}`;
const fileName = screenshotPath.split('/')[1]
const downloadFolder = './images'
if(!fs.existsSync(downloadFolder)){
fs.mkdirSync(downloadFolder);
console.log('Images directory created successfully.');
}
const localFilePath = path.resolve(__dirname, downloadFolder, fileName);
try {
const response = await axios({
method: 'GET',
url: fileUrl,
responseType: 'stream',
});
if(response.status === 200){
isDownloaded = true;
}
await response.data.pipe(fs.createWriteStream(localFilePath));
} catch (error) {
console.log('Error occured while downloading screenshot... : ', error);
}
return { isDownloaded, fileName };
}
const readScreenshot = async(req, res) => {
try {
const {isDownloaded, fileName} = await downloadScreenshot(req.body.temp);
if(isDownloaded){
console.log('__dirname + /images/ + fileName : ', __dirname + '/images/' + fileName )
res
.status(200)
.sendFile(fileName, {root : __dirname + '/images/'} );
} else {
res
.status(500)
.send({
message: 'No screenshot for this offer...'
})
}
} catch (error) {
console.log('Error occured while retrieving screenshot...', error)
res
.status(500)
.send({ message: error });
}
}
module.exports = {
readScreenshot: readScreenshot,
}
I would like to display the required image in my Vue app. Thus, I created the following view: ReadScreenshot.vue
<template>
<div>
<img :src="img">
</div>
</template>
<script>
import Screenshots from '../../services/screenshots.service'
export default {
props: ['id'],
data(){
return {
img: '',
}
},
async mounted(){
console.log(this.id)
const temp = await Screenshots.readScreenshot({ temp: this.id });
console.log(temp)
this.img = temp.data
}
}
</script>
Here is my screenshots.service.js script:
import api from '../../http-common';
export default new class ScreenshotsService {
//Read Screenshot
readScreenshot(screenshotName){
return api.post('read/screenshot', screenshotName)
}
}
Console.log(temp) is returning empty data.
In the screenshots.controller.js file, if I'm forcing the fileName with an existing one in the sendFile function, e.g. '2882LsgIXHOiXiOQ5MSv3R6v1hDijAdG5i756CdG5o7v527i5sS1XZgiXR6i1sSGj.png', I'm receiving a non empty data in my ReadScreenshot.vue .
Even if I'm receiving the data, the image is still not displayed...
How should I proceed, to get this right?
thks for your help

Uploading blob/file in react-native, contents is empty

I am able to succesfully upload a blob with proper contents from my web browser, but when I do it from react-native, the upload file is empty. Here is the code:
async function doit() {
const data = new FormData();
data.append('str', 'strvalue');
data.append(
'f',
new File(['foo'], 'foo.txt', {type: 'text/plain'}),
);
await fetch('http://localhost:3002/upload', {
method: 'POST',
body: data
});
}
However doing this same code from react-native, it uploads, but the file is empty.
Here is the node.js server I am using to test this. Loading http://localhost:3002 gives you a button called "upload it". Clicking it does the upload from the web. Screenshots of results are below.
var multiparty = require('multiparty');
var http = require('http');
http
.createServer(function (req, res) {
if (req.url === '/upload' && req.method === 'POST') {
console.log('multipart here');
var form = new multiparty.Form();
form.parse(req, function (err, fields, files) {
console.log(require('util').inspect({ fields, files }, false, null, true));
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ bar: true }));
});
return;
}
console.log('here');
// show a file upload form
res.writeHead(200, { 'content-type': 'text/html' });
res.end(
`
<script>
async function doit() {
const data = new FormData();
data.append('str', 'strvalue');
data.append(
'f',
// new File([new Blob(['asdf'], {type : 'text/plain'})], 'filename.txt'),
new File(['foo', 'what', 'the', 'hell'], 'foo.txt', {type: 'text/plain'}),
);
const res = await fetch('http://localhost:3002/upload', {
method: 'POST',
body: data
});
console.log(JSON.stringify(res, null, 4));
}
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('b').addEventListener('click', doit, false)
}, false);
</script>
<button type="button" id="b">upload it</button>
`
);
})
.listen(3002);
From web browser we see the node server logs this, notice file size is 14.
However from react-native we see file size is 0:
I faced the same problem recently while posting an image from a react-native app to a server. However, I was able to make it work by appending the name and type of the file to the formData instance.
Here, the uri argument to uploadImageAsync is passed as a route parameter from the previous screen.
const postShoutHandler = async () => {
setShoutUploadStatus("Started Upload");
const response = await uploadImageAsync(route.params.captures);
const uploadResult = await response.json();
if (uploadResult === "Upload successful") {
setShoutUploadStatus("Success");
navigation.navigate("Home");
} else {
setShoutUploadStatus("Failed");
}
};
/* <--Upload image function --> */
const uploadImageAsync = (uri: string) => {
const apiUrl = "https://www.yourserver.com/image";
let uriParts = uri.split(".");
let fileType = uriParts[uriParts.length - 1];
let formData = new FormData();
formData.append("img", {
uri,
name: `photo.${fileType}`,
type: `image/${fileType}`,
});
formData.append("description", "HEY");
let options = {
method: "POST",
body: formData,
headers: {
Accept: "application/json",
"Content-Type": "multipart/form-data",
Authorization: "Bearer " + accessToken,
},
};
return fetch(apiUrl, options);
};
/* <--Upload image function --> */
Here is the Image configuration.
const photoData = await camera.takePictureAsync({
base64: true,
exif: false,
});

Postman can upload file to a Node api. But Not with React

Redux Code
export const uploadImage = data => async dispatch => {
const config = {
headers: {
"Content-Type": "multipart/form-data"
}
};
try {
const res = await axios.post("/api/profile/upload", data, config);
// const res = await axios.post("/api/profile/upload", data, {
// headers: {
// "Content-Type": "multipart/form-data"
// }
// });
console.log(res);
dispatch({
type: AVATAR_UPLOAD,
payload: res.data
});
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, "danger")));
}
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
React Code
const data = new FormData();
data.append("file", avatar);
uploadImage(data);
I get bad request message, req.files is empty
I try to upload file with Postman same configuration,it seems api works but can't upload with react formdata.
Any idea would be appreciated. Thanks in advance
try doing this:
const formData = new FormData();
formData.append('file', file);
const res = await axios.post("/api/profile/upload", formData, config);

Resources