NodeJs Access [object FormData] - javascript

I am trying to post an array of multiple objects (2 strings, 1 date & 1 file) from Vue.3/Axios to A Nodejs server using Multer. However in the Nodejs Server when I console.log req.files and req.body they return
files - []
&
body - [Object: null prototype] { formData: '[object FormData]' }
How do I access my data from that object FormData.
Axios Post
async post(images) {
var formData = new FormData();
for (let i = 0; i < images.length; i++) {
let file = Array.from(images[i].file[0]);
formData.append("file", file);
formData.append("title", images[i].title);
formData.append("project", images[i].project);
formData.append("date", images[i].date);
}
var isValid = true;
if (isValid) {
console.log(
await axios
.post(
`http://localhost:3080/api/image/post`,
{
formData,
},
{
headers: {
"Content-Type": "multipart/form-data",
},
}
)
.then((res) => console.log(res))
.catch((err) => ("Error Occured", err))
);
}
}
Where images is a proxy containing an array with image objects holding:
date: "2022-06-10"
file: FileList {0: File, length: 1}
project: "Project Name"
title: "Test"
Multer is setup like so
const multer = require('multer')
var storage = multer.diskStorage({
destination: function (request, file, callback) {
callback(null, './uploads/');
},
filename: function (request, file, callback) {
callback(null, file.originalname)
}
});
var upload = multer({ storage: storage });
app.post('/api/image/post', upload.array('file'), function (req, res, next)){
console.log(req.body)
}

remove the object property shorthand, use just formData:
await axios
.post(
`http://localhost:3080/api/image/post`,
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
})

Related

How to read files after sending a post request in the req.body

I am running into an issue where when I want to upload an image to s3 bucket nothing goes through.
Basically the only message I get is
API resolved without sending a response for /api/upload/uploadPhoto, this may result in stalled requests.
In the front end, I have an input which can take multiple files ( mainly images ) and then those are stored in event.target.files.
I have a function that stores each file in a state array, and with the button submit it sends a post request to my next.js API.
Here's the logic on the front end:
This function handles the photos, so whenever I add a photo it will automatically add it to the listingPhotos state:
const handleListingPhotos = async (e: any) => {
setMessage(null);
let file = e.target.files;
console.log("hello", file);
for (let i = 0; i < file.length; i++) {
const fileType = file[i]["type"];
const validImageTypes = ["image/jpeg", "image/png"];
if (validImageTypes.includes(fileType)) {
setListingPhotos((prev: any) => {
return [...prev, file[i]];
});
} else {
setMessage("Only images are accepted");
}
}
};
Once the photos are stored in the state, I am able to see the data of the files in the browsers console.log. I run the onSubmit to call the POST API:
const handleSubmit = async (e: any) => {
e.preventDefault();
const formData = new FormData();
formData.append("files[]", listingPhotos);
await fetch(`/api/upload/uploadPhoto`, {
method: "POST",
headers: { "Content-Type": "multipart/form-data" },
body: formData,
}).then((res) => res.json());
};
console.log("listingphotos:", listingPhotos);
Which then uses this logic to upload to the S3 Bucket, but the issue is that when I log req.body I am getting this type of information:
req.body ------WebKitFormBoundarydsKofVokaJRIbco1
Content-Disposition: form-data; name="files[]"
[object File][object File][object File][object File]
------WebKitFormBoundarydsKofVokaJRIbco1--
api/upload/UploadPhoto logic:
import { NextApiRequest, NextApiResponse } from "next";
const AWS = require("aws-sdk");
const access = {
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
};
// creates an S3 Client
const s3 = new AWS.S3({ region: "region", credentials: access });
export default async function uploadPhoto(
req: NextApiRequest,
res: NextApiResponse
) {
// take info from parent page
// console.log("req.body: ", req.body);
if (req.method === "POST") {
console.log("req.body", req.body);
let body = req.body;
let headers = req.headers;
let contentType = headers["Content-Type"] || headers["content-type"];
// check for correct content-type
if (!contentType.startsWith("multipart/form-data")) {
return { statusCode: 400, body: "Invalid content type" };
}
let boundary = contentType.replace("multipart/form-data; boundary=", "");
let parts = body.split(boundary);
for (let part of parts) {
if (part.startsWith("Content-Disposition")) {
let [fileData] = part.split("\r\n\r\n");
fileData = fileData.slice(0, -2);
let [fileName] = part.split("filename=");
fileName = fileName.slice(1, -1);
let params = {
Bucket: "RANDOM BUCKET NAME",
Key: fileName,
Body: fileData,
ContentType: { "image/png": "image/jpg" },
};
// Need to set the PARAMS for the upload
await s3.putObject(params);
console.log(
"Successfully uploaded object: " + params.Bucket + "/" + params.Key
);
}
}
return {
statusCode: 200,
body: "File uploaded",
};
// Uploads the files to S3
}
}
I was able to find a way to read if the files were correctly displayed.
req.body {
fileName: 'b699417375e46286e5a30fc252b9b5eb.png',
fileType: 'image/png'
}
POST request code was changed to the followng:
const s3Promises = Array.from(listingPhotos).map(async (file) => {
const signedUrlRes = await fetch(`/api/upload/uploadPhoto`, {
method: "POST",
body: JSON.stringify({
fileName: file.name,
fileType: file.type,
}),
headers: { "Content-Type": "application/json" },
});
Obviously, this is not the solution but it's part of it. The only problem I am running into right now is handling CORS in order to see if the files are sent to the bucket.

axios vs fetch throwing error on file upload

I am using fetch instead of axios in my react project
my this method working fine with the axios to upload an image on the server
Upload image function
<Upload customRequest={dummyRequest} className="upload-btn-container" onChange={onChange}>
<Button className="btn custom-upload-btn">Upload Image</Button>
</Upload>
const uploadPicture = async (data) =>{
const value = await getUploadPicture(data)
if(value.value.data.status){
await addImage(value.value.data.data)
}
}
const onChange = async (info) => {
for (let i = 0; i < info.fileList.length; i++) {
const data = new FormData();
data.append('file', info.fileList[i]);
data.append('filename', info.fileList[i].name);
setImgName(info.fileList[i].name)
let value = await uploadPicture(data);
}
};
return axios({
method: 'post',
url: `${NewHostName}/upload`,
headers: {
'Content-Type': 'application/json',
'Authorization': localStorage.getItem('authToken')
},
data:data
})
.then(response => {
return response
}).catch(err => {
console.log("err", err)
})
whereas when I do same with the fetch it throws me error on the backend "Cannot read property of split of undefined"
return fetch(`${NewHostName}/upload`, {
method: "post",
headers: {
"Content-Type": "application/json",
Authorization: localStorage.getItem('authToken'),
},
body: JSON.stringify(data),
// body :data
})
.then((res) => {
return res.json();
})
.then((payload) => {
return payload;
})
.catch((err) => {
throw err;
})
Not sure what is the reason behind this
this is my backend upload api
const handler = async (request, reply) => {
try {
const filename = request.payload.filename
const fileExtension = filename.split('.').pop()
AWS.config.update({
accessKeyId: Config.get('/aws').accessKeyId,
secretAccessKey: Config.get('/aws').secretAccessKey,
region: Config.get('/aws').region
})
const s3 = new AWS.S3({
params: {
Bucket: Config.get('/aws').bucket
}
})
const Key = `/${shortid.generate()}.${fileExtension}`
const obj = {
Body: request.payload.file,
Key,
ACL: 'public-read'
}
s3.upload(obj, async (err, data) => {
if (err) {
return reply({ status: false, 'message': err.message, data: '' }).code(Constants.HTTP402)
} else if (data) {
return reply({ status: true, 'message': 'ok', data: data.Location }).code(Constants.HTTP200)
}
})
} catch (error) {
return reply({
status: false,
message: error.message,
data: ''
})
}
}
data is a FormData object.
In your original code you are lying when you say 'Content-Type': 'application/json'. Possibly Axios recognises that you've passed it a FormData object and ignores your attempt to override the Content-Type.
Your fetch code, on the other hand, says body: JSON.stringify(data) which tries to stringify the FormData object and ends up with "{}" which has none of your data in it.
Don't claim you are sending JSON
Don't pass your FormData object through JSON.stringify
For image upload you not use JSON.stringify(data).You can try with formData and append an image file with form data.
var formdata = new FormData();
formdata.append("image", data);
Did you check that
const filename = request.payload.filename
exists?
Is the key really payload? The following does not make any changes to your code:
.then((payload) => {
return payload;
})

how to fix uri undefined error when trying to send request

when I'm trying to call the request in front node, I'm getting error in my backend node " RequestError: Error: Invalid URI "undefined"" , it seems like backend node request is not getting the data form my frontend node request.
knowing that uploadLink already have a value and in my browser console the frontend request looks ok
my backend request code
const ThumbnailUpload = async (req, res) => {
const { Uploadlink } = req.body;
const { selectedFile } = req.body;
const clientServerOptions = {
uri: `${Uploadlink}`,
body: JSON.stringify({
name: selectedFile,
}),
method: 'PUT',
headers: {
'Content-Type': ' application/json',
Accept: 'application/vnd.vimeo.*+json;version=3.4',
Authorization: getVimeoAuthorization(),
},
};
request(clientServerOptions, function (error, response) {
if (error) {
res.send(error);
} else {
const body = JSON.parse(response.body);
res.send(body);
}
console.log(Uploadlink);
});
};
and my frontend code is
const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData();
formData.append(
'selectedFile',
new Blob([selectedFile], { type: 'image/jpg, image/png, or image/gif' }),
);
formData.append('uploadLink', uploadLink);
const headers = {
'Content-Type': 'image/jpg, image/png, or image/gif',
Accept: 'application/vnd.vimeo.*+json;version=3.4',
};
try {
axios
.post(`${backendPostPath}/thumbnail-upload`, formData, {
headers,
})
.then((response) => {
applyThumbnial();
console.log(response);
});
} catch (error) {
console.log(error);
}
};
any advise ?
change:
const { Uploadlink } = req.body;
to:
const { uploadlink } = req.body;
make variable consistent throughout the code
EDIT
also, since you're uploading a file, you need to use upload middleware before request handler, and file will be within req.file:
route.post('/thumbnail-upload', upload.single('selectedFile'), ThumbnailUpload);
//... handler..
const selectedFile = req.file;

Unable to load file due to Multipart: Boundary not found

I am trying to upload images from desktop but the error for multipart boundary not found. How to set a boundary for image uploading? This is my first time doing image uploading so please advice.
html event listener when user upload image
document.getElementById('image-file').addEventListener('change',
async (e) => {
const file = e.target.files[0]
const bodyFormData = new FormData()
bodyFormData.append('image', file)
showLoading()
const data = await uploadProductImage(bodyFormData)
hideLoading()
if (data.error) {
showMessage(data.error)
} else {
showMessage('Image Uploaded Successfully.')
console.log(data)
document.getElementById('image').value = data.image
}
})
},
api.js - posting action
export const uploadProductImage = async (bodyFormData) => {
const options = {
method: 'POST',
body: bodyFormData,
headers: {
'Content-Type': 'multipart/form-data',
},
}
try {
const response = await fetch(`${apiUrl}/api/uploads`, options)
const json = await response.json()
if (response.status !== 201) {
throw new Error(json.message)
}
return json
} catch (err) {
console.log('Error in upload image', err.message)
return { error: err.message }
}
}
uploadroute.js - for uploading image
import express from 'express'
import multer from 'multer'
const storage = multer.diskStorage({
destination(req, file, cb) {
cb(null, 'uploads/')
},
filename(req, file, cb) {
cb(null, `${Date.now()}.png`)
},
})
const upload = multer({ storage })
const router = express.Router()
router.post('/', upload.single('image'), (req, res) => {
res.status(201).send({ image: `/${req.file.path}` })
})
export default router
When you're sending a form with fetch in the frontend, don't set Content-Type header yourself. If you do, it won't have the form boundary and the multipart/form-data request will be parsed incorrectly in the backend.
You can omit the header because the browser will set it for you, which includes a unique boundary. For example:
Your header: Content-Type=multipart/form-data;
Browser's header: Content-Type=multipart/form-data; boundary=------WebKitFormBoundaryg7okV37G7Gfll2hf--
In your case:
const options = {
method: 'POST',
body: bodyFormData,
}
const response = await fetch(`${apiUrl}/api/uploads`, options)

How to correctly send an image to the backend (nodejs) using multer in React Native

I'm having trouble sending a photo, in a PUT route, be it with Axios or Fetch, anyway, my Nodejs backend is configured and the whole upload process works normal testing by Insonmia, but in React Native It does not work properly.
BACKEND - CONTROLLER FOR UPDATING USER DATA
async updateUserData(req: Request, res: Response, next: NextFunction) {
const { id } = req.params;
const { username, status } = req.body as IBodyData;
try {
const userExists = await knex('tb_user')
.where('id', Number(id))
.first();
if (!userExists) {
return res.status(400).json({ error: 'User does not exist.' });
}
const serializedUserInfo = {
photo: `${process.env.BASE_URL}/uploads/${req.file.filename}`,
username,
status,
};
const updateInfo = await knex('tb_user')
.update(serializedUserInfo)
.where('id', Number(id));
if (!updateInfo) {
return res.status(400).json({ error: 'Error updating data.' });
}
return res.json(updateInfo);
} catch(err) {
return res.status(500).json({ error: 'Error on updating user.' });
}
}
BACKEND - ROUTE
routes.put(
'/updateuser/:id',
multer(multerConfig).single('userphoto'),
UserController.updateUserData
);
CONFIG MULTER
export default {
fileFilter: (req: Request, file, cb) => {
const allowedMimes = [
'image/jpeg',
'image/jpg',
'image/pjpeg',
'image/png',
];
if (!allowedMimes.includes(file.mimetype)) {
return cb(new Error('Invalid file type.'));
}
return cb(null, true);
},
limits: {
fileSize: 2 * 1024 * 1024,
},
storage: multer.diskStorage({
destination: resolve(__dirname, '..', '..', 'uploads'),
filename: (req: Request, file, cb) => {
const filename = `${randomBytes(6).toString('hex')}-${file.originalname}`;
return cb(null, filename);
}
})
} as Options;
REACT NATIVE - FUNCTION FOR GET DATA OF PHOTO AND SET ON STATE
function handleUploadImage(image: IImagePickerResponse) {
if (image.error) {
return;
}
if (image.didCancel) {
return;
}
if (!image.uri) {
return;
}
setSelectedImage({
fileName: image.fileName,
fileSize: image.fileSize,
type: image.type,
uri: image.uri,
});
}
REACT NATIVE - FUNCTION FOR SEND DATA IN API
async function handleSaveChange() {
try {
const formData = new FormData();
formData.append('userphoto', {
uri:
Platform.OS === 'android'
? selectedImage?.uri
: selectedImage?.uri?.replace('file://', ''),
type: selectedImage.type,
name: selectedImage.fileName,
});
formData.append('username', usernameInput);
formData.append('status', statusInput);
console.log(formData);
await api.put(`/updateuser/${userData?.id}`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
// const updateUser = await fetch('http://192.168.2.8:3333/updateuser/1', {
// method: 'POST',
// headers: {
// 'Content-Type': 'multipart/form-data',
// },
// body: formData,
// })
// .then((response) => {
// response.json();
// })
// .then((response) => {
// console.log(response);
// })
// .catch((err) => console.log(err));
return Toast.success('Informações alteradas com sucesso.');
} catch (err) {
const {error} = err.response.data;
return Toast.error(error);
}
}
obs: A note to take into account is that in the formData.append ('userphoto', value) part if the value has sent a normal json object, the request is neither made nor the Network Error error even IP address being correct the port too, anyway, and if I use formData.append ('userphoto', JSON.stringfy(value)) the request is made normally but in the backend the photo arrives as undefined and the rest of fields are sent and received normal.
I really tried several things and I couldn't, I changed the type of the PUT method to POST without success, add 'Accept': 'application/json' without success, as I said, the normal sending by Insomnia is working.
Finally, here is the lib configuration I used to get the photo:
REACT NATIVE IMAGE PICKER
ImagePicker.showImagePicker(
{
title: 'Selecione uma imagem',
takePhotoButtonTitle: 'Tirar foto',
chooseFromLibraryButtonTitle: 'Escolher da galeria',
cancelButtonTitle: 'Cancelar',
noData: true,
// storageOptions: {
// skipBackup: true,
// path: 'images',
// cameraRoll: true,
// waitUntilSaved: true,
// },
},
handleUploadImage,
)
SOLVED
Apparently it was a problem with the version of React Native, I am currently using version 0.62.XX
To resolve, just comment line number 43 in the file:
android/app/src/debug/java/com/mobile/ReactNativeFlipper.java
The code is this:
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor (networkFlipperPlugin));

Categories