I am trying to upload a profile image of a user from React Native App(Android). I am new to uploading image so I searched and found a package called multer. As I am using Mongodb Atlas, multer has a storage option of MemoryStorage and using MemoryStorage I tried uploading the image to Atlas.But the problem I am facing is that I dont the format in which I have to send the data to Nodejs. I use FormData() to send the image details to Nodejs.I may be wrong in choosing the format or package or anywhere.Please correct me if find me wrong anywhere.Below is code for everything.
avatar.js
const avatarSchema = new Schema(
{
profile_avatar: {
type: Buffer,
contentType: String,
// required: [true, 'fee is required'],
},
},
{
timestamps: true,
},
);
const avatar = mongoose.model('avatar', avatarSchema);
module.exports = avatar;
avatar route
let Avatar = require('../models/avatar');
const multer = require('multer');
var storage = multer.memoryStorage();
var upload = multer({storage: storage});
router.route('/add/image').post(upload.single('avatar'), (req, res, next) => {
console.log(req); // it does not get hit
var newAvatar = new Avatar();
newAvatar.profile_avatar = req.file.buffer;
newAvatar.save(function(err) {
console.log('error', err);
if (err) {
console.log(error);
} else {
res.send({message: 'uploaded successfully'});
}
});
});
React Native Code
//Function to select single image from gallery(its working fine)
imagePick = () => {
let options = {
quality: 0.2,
maxWidth: 500,
maxHeight: 500,
allowsEditing: true,
noData: true,
storageOptions: {
skipBackup: false,
path: 'images',
},
};
ImagePicker.launchImageLibrary(options, response => {
console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton);
alert(response.customButton);
} else {
this.setState({
imageData: response,
});
}
});
};
//Function to structure FormData
createFormData = photo => {
const data = new FormData();
data.append('avatar', {
name: photo.fileName,
type: photo.type,
uri:
Platform.OS === 'android'
? photo.uri
: photo.uri.replace('file://', ''),
};);
return data;
};
//Function to send Image data to Nodejs(Not Working)
sendImage = () => {
fetch('http://10.0.2.2:5000/user/add/image', {
method: 'POST',
credentials: 'same-origin',
redirect: 'follow',
referrer: 'no-referrer',
body: this.createFormData(this.state.imageData),
})
.then(response => response.json())
.then(response => {
console.log('upload succes', response);
Alert.alert('Upload success!');
})
.catch(error => {
console.log('upload error', error);
Alert.alert('Upload failed!');
});
};
Any other way I can upload the image? Any help would be appreciated.
Related
Like I said on the title, I'm facing that error when I'm trying to upload a file from the client-side. Here are the code blocks for more context:
Route:
const express = require("express");
const { upload } = require("../middlewares/uploadMiddleware");
const { registerResearchSheet } = require("../controllers/researchSheetControllers");
const router = express.Router();
router.route("/").post(upload.single("file"), registerResearchSheet);
Middleware:
const multer = require("multer");
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "backend/uploads/");
},
filename: function (req, file, cb) {
cb(
null,
new Date().toISOString().replace(/:/g, "-") + "-" + file.originalname
);
},
});
const fileFilter = (req, file, cb) => {
if (file.mimetype === "application/pdf") {
cb(null, true);
} else {
cb(null, false);
}
};
const upload = multer({ storage: storage, fileFilter: fileFilter });
module.exports = { upload };
Controller:
const asyncHandler = require("express-async-handler");
const ResearchSheet = require("../models/researchSheetModel");
const registerResearchSheet = asyncHandler(async (req, res) => {
const {
title,
director,
coordinator,
students,
reviewers,
proposalType,
investigationType,
status,
} = req.body;
if (!req.file) {
res.status(400);
throw new Error("The file hasn't be uploaded correctly"); **//This is the triggering error**
}
const file = req.file.path;
const researchSheetExists = await ResearchSheet.findOne({ title, students });
if (researchSheetExists) {
res.status(400);
throw new Error("Title or students can't be repeated");
}
const researchSheet = await ResearchSheet.create({
title,
file, // file: buffer,
director,
coordinator,
students,
reviewers,
proposalType,
investigationType,
status,
});
if (researchSheet) {
res.status(201).json({
_id: researchSheet._id,
title: researchSheet.title,
file: researchSheet.file,
director: researchSheet.director,
coordinator: researchSheet.coordinator,
students: researchSheet.students,
reviewers: researchSheet.reviewers,
proposalType: researchSheet.proposalType,
investigationType: researchSheet.investigationType,
status: researchSheet.status,
});
} else {
res.status(400);
throw new Error("An error has ocurred");
}
});
And finally the frontend part:
const [file, setFile] = useState(null);
//code..
const singleFileUpload = (e) => {
setFile(e.target.files[0]);
};
const submitHandler = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("title", title);
formData.append("file", file);
formData.append("director", director);
formData.append("coordinator", coordinator);
formData.append("students", students);
formData.append("proposalType", proposalType);
formData.append("investigationType", investigationType);
console.log(file); //This log is correctly showing the File and its properties
if (
!title ||
!coordinator ||
!students ||
!proposalType ||
!investigationType
) {
setMessage("You must fill all the fields");
} else {
dispatch(registerResearchSheetAction(formData));
// history.push("/listresearchsheets");
}
};
//more code...
<Form.Control
className="mb-3"
type="file"
accept=".pdf"
onChange={(e) => singleFileUpload(e)}
/>
<Button type="submit">Register</Button>
Action:
export const registerResearchSheetAction =
(
title,
file,
director,
coordinator,
students,
reviewers,
proposalType,
investigationType,
status
) =>
async (dispatch) => {
try {
dispatch({
type: RESEARCHSHEET_REGISTER_REQUEST,
});
const formData = new FormData();
formData.append("file", file);
formData.append("title", title);
formData.append("director", director);
formData.append("coordinator", coordinator);
formData.append("students", students);
formData.append("reviewers", reviewers);
formData.append("proposalType", proposalType);
formData.append("investigationType", investigationType);
formData.append("status", status);
const config = {
headers: {
"Content-Type": "multipart/form-data",
},
};
const { data } = await axios.post(
"/api/researchsheets",
formData,
config
);
dispatch({ type: RESEARCHSHEET_REGISTER_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: RESEARCHSHEET_REGISTER_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
I already tested the functionality on the server-side using Postman and it is uploading, more than the file, the route/path of it successfully on the database. That's why I'm thinking that the problem must be on the client-side. I really don't know what I'm missing here, so that's why I came for a little of help, I hope I was clear on my explanaiton.
Thank you in advance!
I try to implement multer to upload files for my galery.
Unfortunately it doesn't work.
Network shows me this error:
msg: "Cannot read properties of undefined (reading 'filename')"
I guess the error is somewhere here:
upload.single('galery')
Bcs i try to console.log my file insite the storage function and it seems like it's not used at all. I couldn't even print a "Hello World" in there atm.
That's why I'm getting an error after:
const image = req.file.filename;
const router = require('express').Router();
const galeryCtrl = require('../controllers/galeryCtrl');
const multer = require('multer');
const shortid = require('shortid');
const path = require('path');
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '../images');
},
filename: function (req, file, cb) {
console.log(file);
cb(
null,
shortid.generate() + '-' + Date.now() + path.extname(file.originalname)
);
},
});
const fileFilter = (req, file, cb) => {
const allowedFileTypes = ['image/jpeg', 'image/jpg', 'image/png'];
if (allowedFileTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(null, false);
}
};
let upload = multer({ storage: storage, fileFilter });
router
.route('/galery')
.get(galeryCtrl.getGaleries)
.post(upload.single('galery'), async (req, res) => {
try {
const image = req.file.filename;
const galeryCheck = await Galery.findOne({ image });
// Check if already exist
if (galeryCheck)
return res.status(400).json({ msg: 'already exists' });
const newGalery = new Galery({ image, name: shortid.generate() });
await newGalery.save();
res.json({ msg: 'success' });
} catch (err) {
return res.status(500).json({ msg: err.message });
}
});
const onFileChange = (e) => {
setGalery(e.target.files[0]);
};
const onSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('galery', galery);
console.log(formData);
axios({
url: '/api/galery',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data',
},
data: formData,
})
.then((response) => {
return response;
})
.catch((error) => {
console.log(error);
});
};
<form onSubmit={onSubmit}>
<input type='file' onChange={onFileChange} />
<button type='submit'>Submit</button>
</form>
I tried to change the code but this brings new errors.
First i was need to remove the value={galery} from my input, otherwise I am getting a white screen.
But now I've new errors:
POST http://localhost:3000/api/galery 500 (Internal Server Error)
Error: Request failed with status code 500 (from the console.log(error)
Backend code looks good to me but while sending the file in the request can you try like below.
onFileChange(e) {
setGalery(e.target.files[0])
}
onSubmit(e) {
e.preventDefault()
const formData = new FormData()
formData.append('galery', galery)
axios.post("serverurlhere", formData, {
}).then(res => {
console.log(res)
})
}
// OR ANOTHER WAY
onSubmit(e) {
e.preventDefault()
const formData = new FormData();
formData.append('galery', galery)
axios({
url:"serverurlhere",
method:'post',
headers:{
'Content-Type': 'multipart/form-data'
},
data: formData,
})
.then(response => {
return response;
})
.catch(error =>{
console.log(error);
});
}
<form onSubmit={this.onSubmit}>
<input type="file" onChange={this.onFileChange} />
<button type="submit">Submit</button>
</form>
I can generate the pdfBuffer by sending the HTML to the backend, but receiving the data and sharing it with expo share is where I‘m stuck at. Really Apreciate for any help.
The lib I'm using is
var pdf = require('html-pdf')
https://github.com/marcbachmann/node-html-pdf
I have tried the following code and search google for 4 days with no prevail.
frontend react native code
const createPdf = () => {
const html = insertHtml(orders)
axios('http://573f-98-14-177-227.ngrok.io/api/createPdf', {
method: 'post',
data: { html },
responseType: 'json',
})
.then((res) => {
console.log('pdf ', res.data)
sharePdf(res.data)
})
.catch(function (error) {
console.log('err ', error)
})
Alert.alert('Ordered Success', 'press ok to save a copy', [
{
text: 'Ok',
onPress: () => {},
style: 'cancel',
},
{
text: 'Cancel',
style: 'cancel',
},
])
props.resetOrder()
setOrders([])
}
Node backend to create the pdf
router.post('/createPdf', (req, res) => {
const {html} = req.body
pdf.create(html).toBuffer((err, buffer) => {
res.setHeader('Content-Length', buffer.length)
res.setHeader('Content-type', 'application/pdf')
var pdfBuffer = new Buffer.from(buffer)
res.send(pdfBuffer)
console.log('buffer ', pdfBuffer)
})
pdf.create(html).toStream((err, pdfStream) => {
if (err) {
console.log(err)
return res.sendStatus(500)
} else {
res.statusCode = 200
pdfStream.on('end', () => {
return res.end()
})
pdfStream.pipe(res)
}
})
})
I want to use a library called react-native-image-picker to deliver the image to the backend server using formData and save it to the computer diskStorage upload using multer.
However, there is some confusion in using this. If I use my code I get this error:
MulterError: Unexpected fielderror.
How can I fix the code?
this is my code
(front/Third.js)
const Third = () => {
const [imageSource, setImageSource] = useState(undefined);
const dispatch = useDispatch();
const options = {
title: 'Load Photo',
customButtons: [
{name: 'button_id_1', title: 'CustomButton 1'},
{name: 'button_id_2', title: 'CustomButton 2'},
],
storageOptions: {
skipBackup: true,
path: 'images',
},
};
const showCameraRoll = () => {
launchImageLibrary(options, (response) => {
if (response.error) {
console.log('LaunchImageLibrary Error: ', response.error);
} else {
setImageSource(response.uri);
}
console.log('response.uri:', response.uri);
// response.uri: file:///data/user/0/com.cookingrn/cache/rn_image_picker_lib_temp_5f6898ee-a8d4-48c9-b265-142efb11ec3f.jpg
const form = new FormData();
form.append('Files', {
name: 'SampleFile.jpg', // Whatever your filename is
uri: response.uri, // file:///data/user/0/com.cookingrn/cache/rn_image_picker_lib_temp_5f6898ee-a8d4-48c9-b265-142efb11ec3f.jpg
type: 'image/jpg', // video/mp4 for videos..or image/png etc...
});
dispatch({
type: UPLOAD_IMAGES_REQUEST,
data: form,
});
});
};
return (
<Container>
{imageSource && <Photo source={{uri: imageSource}} />}
<ImagePickerButton onPress={showCameraRoll}>
<Label>Show Camera Roll</Label>
</ImagePickerButton>
</Container>
);
};
export default Third;
(backend/post.js)
try {
fs.accessSync('uploads');
} catch (error) {
console.log('uploads 폴더가 없으므로 생성합니다.');
fs.mkdirSync('uploads');
}
const upload = multer({
storage: multer.diskStorage({
destination(req, file, done) {
done(null, 'uploads');
},
filename(req, file, done) {
const ext = path.extname(file.originalname);
const basename = path.basename(file.originalname, ext);
done(null, basename + '_' + new Date().getTime() + ext);
},
}),
limits: {fileSize: 20 * 1024 * 1024},
});
router.post('/images', isLoggedIn, upload.array('image'), (req, res, next) => {
// dispatch UPLOAD_IMAGES_REQUEST POST /post/images
res.json(req.files.map((v) => v.filename));
});
(saga/index.js)
function uploadImagesAPI(data) {
return axios.post('/post/images', data);
}
function* uploadImages(action) {
try {
const result = yield call(uploadImagesAPI, action.data);
yield put({
type: UPLOAD_IMAGES_SUCCESS,
data: result.data,
});
} catch (err) {
console.error(err);
yield put({
type: UPLOAD_IMAGES_FAILURE,
error: err.response.data,
});
}
}
Create a FormData and send a post request to backend by including that form in the body of the request
const SendFileToBackend = (uri) => {
const form = new FormData();
form.append("Files", {
name: "SampleFile.jpg", // Whatever your filename is
uri: uri, // file:///data/user/0/com.cookingrn/cache/rn_image_picker_lib_temp_5f6898ee-a8d4-48c9-b265-142efb11ec3f.jpg
type: "image/jpg", // video/mp4 for videos..or image/png etc...
});
// Perform a Post request to backend server by putting `form` in the Body of the request
};
The goal is to have users enter in some information into a form and spit that out into a PDF. I'm using JSPDF to parse and create the PDF. I've successfully gotten my code to make a printable PDF, but in an effort to not have paper floating around the office, I made a cloud function to instead email that PDF to the customer.
Here is my code on the front end. maildoc is the pdf that I've made, it hasn't been printed or anything. So it only exists in memory.
mailDoc = mailDoc.output('datauri');
mailFunction += "&?data=" + mailDoc;
//axios request to the cloud function
axios.get(mailFunction).then( function (response) {
console.log(response);
}).catch(function (error) {
console.log(error)
})
And here is my code on the cloud function
exports.sendMail = functions.https.onRequest((req, res) => {
cors(req, res, () => {
// getting dest email by query string
//?dest= DestinationEmail
const dest = req.query.dest;
const data = req.query.data;
const mailOptions = {
from: 'whatever <whatever#hoobashaka.com>',
to: dest,
subject: "You're Equipment Return to HBCI", // email subject
attachments :[
{
filename: 'return.pdf',
contentType: 'application/pdf',
path: data,
}
],
};
return transporter.sendMail(mailOptions, (erro, info) => {
if(erro){
return res.send(erro.toString());
}
return res.send('Sended');
});
});
If I try and send the data via URI, I get a 413 error, probably because that URI is enormous. But I can't think of another way of sending that generated PDF to the function.
On your client, instead of uploading the file as a datauri, I'd instead use POST and send the PDF inside the request body (just as if you had submitted a file using a form).
mailDocBlob = mailDoc.output('blob');
const data = new FormData();
data.set('dest', someEmail);
data.append('file', mailDocBlob, 'return.pdf');
axios({
method: 'post',
url: 'https://your-cloud-function.here/sendMail',
data: data,
headers: {
'Content-Type': `multipart/form-data; boundary=${data._boundary}`,
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
On the server you would handle the multipart form data using the busboy package.
const BusBoy = require('busboy');
exports.sendMail = functions.https.onRequest((req, res) => {
cors(req, res, (err) => {
if (err) {
// CORS failed. Abort.
console.log("CORS failed/rejected");
res.sendStatus(403); // 403 FORBIDDEN
return;
}
if (req.method !== 'POST') {
res.set('Allow', 'POST, OPTIONS').sendStatus(405); // 405 METHOD_NOT_ALLOWED
return;
}
let busboy = new BusBoy({headers: req.headers, limits: {files: 1}}); // limited to only a single file
const mailOptions = {
from: 'whatever <whatever#hoobashaka.com>',
to: dest,
subject: "Your Equipment Return to HBCI", // email subject - fixed typo
attachments: []
};
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
// add new file attachment
mailOptions.attachments.push({
filename: 'return.pdf',
contentType: 'application/pdf',
content: file, // file is a stream
});
})
.on('finish', () => {
if (mailOptions.attachments.length == 0) {
// not enough attachments
res.status(400).send('Error: not enough attachments');
return;
}
return transporter.sendMail(mailOptions, (erro, info) => {
if (erro) {
return res.status(500).send('Error: ' + erro.toString());
}
return res.send('Sent');
})
})
.on('error', (err) => {
console.error(err);
res.status(500).send('Error: ' + err.code);
});
req.pipe(busboy);
});