sent file from React to Node and got empty obj - node.js

I am getting img data and send this data to the server
console.log shows that data exists
const [fileData, setFileData] = useState("");
console.log("fileData:", fileData);
const getFile = (e: any) => {
setFileData(e.target.files[0]);
};
const uploadFile = (e: any) => {
e.preventDefault();
const data = new FormData();
data.append("file", fileData);
axios({
method: "POST",
url: "http://localhost:5000/api/setImage",
data: data,
headers: {
"content-type": "multipart/form-data", // do not forget this
},
}).then((res) => {
alert(res.data.message);
});
};
server endpoint
router.post("/setImage", userController.setImage);
async setImage(req, res, next) {
try {
let uploadFile = req.body;
console.log(uploadFile);
} catch (e) {
next(e);
}
}
console.log shows empty object but I'm waiting img data

Try using multer with fs and tempy.
router.post("/setImage", multer({ dest: tempy.file() }).single("file"), async (req, res, next) => {
if (!req.file) return
fs.readFile(req.file.path, function (err, filedata) {
if (!req.file) return
// Here you should get your expected data in filedata variable
})
})

Related

Upload image in react native and Express

I use expo document picker to pick a file, and multer in the back end to save it into a folder.
When I send the image, I get a 200 status response but the image is not saved.
What have I been doing wrong ?
React native code :
const pickPicture = async () => {
let result = await DocumentPicker.getDocumentAsync({});
console.log(result.file);
console.log(result);
const data = new FormData();
data.append("avatar", {
uri: result.uri,
type: result.type,
name: result.fileName,
file: result.file,
});
axios
.post(
`http://localhost:8000/uploadAvatar`,
data,
{
withCredentials: true,
headers: {
"Content-Type": "multipart/form-data",
},
transformRequest: (infos, error) => {
return data;
},
}
)
.catch(function (error) {
console.log(error);
})
.then(function (res) {
console.log(res);
});
Node code :
const storage = multer.diskStorage({
destintion: (req, file, callback) => {
callback(null, "avatars")
},
filename: (req, file, callback) => {
callback(null, Date.now() = path.extname(file.originalname));
}
});
const upload = multer({storage: storage});
app.use("/uploadAvatar", upload.single("avatar"), (req, res) => {
res.status(200).json("Image enregistrée !")
})
Path of the image to upload result.file returned by DocumentPicker is a reference of the image to the device storage not actually image data.
It required to fetch raw image data as BLOB and upload BLOB data to the server.
let result = await DocumentPicker.getDocumentAsync({});
console.log(result.file);
console.log(result);
const imageData = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function () {
resolve(xhr.response);
};
xhr.onerror = function (e) {
reject(new TypeError("Network request failed"));
};
xhr.responseType = "blob";
xhr.open("GET",result.file, true);
xhr.send(null);
});
const data = new FormData();
data.append("avatar", {
uri: result.uri,
type: result.type,
name: result.fileName,
file: imageData,
});

multer req file is undefined but why?

Can anyone tell me why my req.file is undefined?
I add the content header type,
I add single.photo but I get no data
frontend:
## data is equal to this:
Object {
"filename": "3ccc61e1-3c49-4538-9594-b4987b3fa66f.jpg",
"type": "image/jpg",
"uri": "file:///data/user/0/host.exp.exponent/cache/ExperienceData/UNVERIFIED-xxxxx-test/ImagePicker/3ccc61e1-3c49-4538-9594-b4987b3fa66f.jpg",
}
const uploadStory = async data => {
try {
const form = new FormData();
form.append('photo', data);
const options = {
headers: {
'Content-Type':'multipart/form-data',
'Accept':'application/json'
},
method: 'POST',
body: form
};
const res = await fetch('http://xxxxxxx.xx:3000/createstory', options);
const out = await res.json();
return out;
} catch(e) {
console.log(e);
return e;
}
};
export default uploadStory;
backend:
const multer = require('multer');
const storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, 'uploads/')
},
filename: function(req, file, cb) {
console.log(req);
cb(null, `${file.filename}-${Date.now()}`)
}
});
const upload = multer({
storage: storage
}).single('photo');
module.exports = async (req, res, next) => {
upload(req, res, function(err) {
if(err) {
console.log(err)
return;
} else if (err instanceof multer.MulterError) {
console.log(err);
} else {
console.log(req.file);
console.log('hi')
}
});
};
..................................
..................................
..................................
..................................
Make sure your data object looks like this
{
name: "3ccc61e1-3c49-4538-9594-b4987b3fa66f.jpg",
type: "image/jpg",
uri: "file:///data/user/0/host.exp.exponent/cache/ExperienceData/UNVERIFIED-xxxxx-test/ImagePicker/3ccc61e1-3c49-4538-9594-b4987b3fa66f.jpg",
}
In your data Object your key is filename instead of name
Change your upload to this
const upload = multer({
storage: storage,
});
And then,
module.exports = upload.single('photo'), async (req, res) => {
try {
console.log(req.body); // Body Here
console.log(req.file); // File Here
res.status(500).send('Fetch Completed Successfully');
} catch (error) {
console.log(error);
res.status(500).send('Error');
}
}

Multiple file upload to S3 with Node.js & Busboy

I'm trying to implement an API endpoint that allows for multiple file uploads.
I don't want to write any file to disk, but to buffer them and pipe to S3.
Here's my code for uploading a single file. Once I attempt to post multiple files to the the endpoint in route.js, it doesn't work.
route.js - I'll keep this as framework agnostic as possible
import Busboy from 'busboy'
// or const Busboy = require('busboy')
const parseForm = async req => {
return new Promise((resolve, reject) => {
const form = new Busboy({ headers: req.headers })
let chunks = []
form.on('file', (field, file, filename, enc, mime) => {
file.on('data', data => {
chunks.push(data)
})
})
form.on('error', err => {
reject(err)
})
form.on('finish', () => {
const buf = Buffer.concat(chunks)
resolve({
fileBuffer: buf,
fileType: mime,
fileName: filename,
fileEnc: enc,
})
})
req.pipe(form)
})
}
export default async (req, res) => {
// or module.exports = async (req, res) => {
try {
const { fileBuffer, ...fileParams } = await parseForm(req)
const result = uploadFile(fileBuffer, fileParams)
res.status(200).json({ success: true, fileUrl: result.Location })
} catch (err) {
console.error(err)
res.status(500).json({ success: false, error: err.message })
}
}
upload.js
import S3 from 'aws-sdk/clients/s3'
// or const S3 = require('aws-sdk/clients/s3')
export default (buffer, fileParams) => {
// or module.exports = (buffer, fileParams) => {
const params = {
Bucket: 'my-s3-bucket',
Key: fileParams.fileName,
Body: buffer,
ContentType: fileParams.fileType,
ContentEncoding: fileParams.fileEnc,
}
return s3.upload(params).promise()
}
I couldn't find a lot of documentation for this but I think I've patched together a solution.
Most implementations appear to write the file to disk before uploading it to S3, but I wanted to be able to buffer the files and upload to S3 without writing to disk.
I created this implementation that could handle a single file upload, but when I attempted to provide multiple files, it merged the buffers together into one file.
The one limitation I can't seem to overcome is the field name. For example, you could setup the FormData() like this:
const formData = new FormData()
fileData.append('file[]', form.firstFile[0])
fileData.append('file[]', form.secondFile[0])
fileData.append('file[]', form.thirdFile[0])
await fetch('/api/upload', {
method: 'POST',
body: formData,
}
This structure is laid out in the FormData.append() MDN example. However, I'm not certain how to process that in. In the end, I setup my FormData() like this:
Form Data
const formData = new FormData()
fileData.append('file1', form.firstFile[0])
fileData.append('file2', form.secondFile[0])
fileData.append('file3', form.thirdFile[0])
await fetch('/api/upload', {
method: 'POST',
body: formData,
}
As far as I can tell, this isn't explicitly wrong, but it's not the preferred method.
Here's my updated code
route.js
import Busboy from 'busboy'
// or const Busboy = require('busboy')
const parseForm = async req => {
return new Promise((resolve, reject) => {
const form = new Busboy({ headers: req.headers })
const files = [] // create an empty array to hold the processed files
const buffers = {} // create an empty object to contain the buffers
form.on('file', (field, file, filename, enc, mime) => {
buffers[field] = [] // add a new key to the buffers object
file.on('data', data => {
buffers[field].push(data)
})
file.on('end', () => {
files.push({
fileBuffer: Buffer.concat(buffers[field]),
fileType: mime,
fileName: filename,
fileEnc: enc,
})
})
})
form.on('error', err => {
reject(err)
})
form.on('finish', () => {
resolve(files)
})
req.pipe(form) // pipe the request to the form handler
})
}
export default async (req, res) => {
// or module.exports = async (req, res) => {
try {
const files = await parseForm(req)
const fileUrls = []
for (const file of files) {
const { fileBuffer, ...fileParams } = file
const result = uploadFile(fileBuffer, fileParams)
urls.push({ filename: result.key, url: result.Location })
}
res.status(200).json({ success: true, fileUrls: urls })
} catch (err) {
console.error(err)
res.status(500).json({ success: false, error: err.message })
}
}
upload.js
import S3 from 'aws-sdk/clients/s3'
// or const S3 = require('aws-sdk/clients/s3')
export default (buffer, fileParams) => {
// or module.exports = (buffer, fileParams) => {
const params = {
Bucket: 'my-s3-bucket',
Key: fileParams.fileName,
Body: buffer,
ContentType: fileParams.fileType,
ContentEncoding: fileParams.fileEnc,
}
return s3.upload(params).promise()
}

How to add item with image with react-redux-saga with nodejs/multer

I am trying to save an item that has images in the form. I was able to save the items using postman, but getting issue when trying from react-redux-saga. I am using ant design for the form.
Front-end code:
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
const { fileList } = this.state;
const formData = new FormData();
fileList.forEach(file => {
formData.append("files[]", file);
});
const postItemData = { ...values, images: formData };
this.props.createItem(postItemData);
}
});
};
saga:
When I try to console, I can get the item details with the image field as a formdata
{
let itemData = yield select(makeSelectPostItemData());
const token = yield select(makeSelectToken());
console.log(itemData, "itemData");
const response = yield call(request, `/item`, {
method: "POST",
headers: {
Authorization: `${token}`
},
body: JSON.stringify(itemData)
});
const successMessage = "Item saved successfully!";
yield put(postItemSuccess(response, successMessage));
}
nodejs:
const upload = multer({
storage: storage,
limits: { fileSize: 1000000 },
fileFilter: function(req, file, cb) {
checkFileType(file, cb);
}
}).array("images");
upload(req, res, err => {
if (err) {
console.log(err, "error");
res.send({ msg: err });
} else {
console.log(req, "req");
if (req.files === undefined) {
res.send({ msg: "Error: No file selected!" });
} else {
const { errors, isValid } = validatePostItem(req.body);
// check validation
if (!isValid) {
return res.status(400).json(errors);
}
const files = req.files;
}
}
});
On the last line const files = req.files. When I try from postman, I get file details, but when I try to request it from react, I have no req.files. How can I get the req.files from the react as I am getting from postman?
I have found the solution for this. In the handleSubmit, I have following code:
fileList.forEach(file => {
formData.append("files[]", file);
});
const postItemData = { ...values, images: formData };
this.props.createItem(postItemData);
Instead of spreading values and inserting the formData in images, what I did was put the all of them into formData.
fileList.forEach(file => {
formData.append("files[]", images);
});
formData.append("name", values.name);
formData.append("price", values.price);
formData.append("detail", values.detail);
this.props.createItem(formData);
With this, I could get req.files and req.body.name, req.body.price, req.body.detail in the node side. Quite different from the postman, but this worked for me.

Axios POSTed FormData has empty body on serverside

This is the client side code. Data is not empty, file is getting uploaded correctly.
export function addGame(data) {
return dispatch => {
const formData = new FormData();
formData.append("game.cover", data.gameCover[0]);
formData.append("game.title", data.gameTitle);
formData.append("game.price", data.gamePrice);
formData.append("game.description", data.description);
return axios.post(apiUrl + "/games/add", formData).then(res => {
dispatch({ type: ADD_GAME, payload: res.data.game });
});
};
}
and this is the serverside
router.post("/add", auth, async (req, res) => {
const body = await req.body;
console.log(body);
let formErrors = [];
if (!body.gameTitle) formErrors.push("Game title is required.");
if (!body.description) formErrors.push("Description is required.");
if (!body.gamePrice) formErrors.push("Price is required.");
if (formErrors.length) res.status(400).send({ success: false, formErrors });
else {
let gameCoverFileName;
if (!fileUpload(req, gameCoverFileName))
formErrors.push("Failed to upload file");
const result = await gameModel.create({
title: body.gameTitle,
cover: gameCoverFileName,
price: body.gamePrice,
description: body.description
});
if (result)
res.status(201).send({
success: true,
game: {
gameTitle: result.title,
gameCover: gameCoverFileName,
gamePrice: result.price,
description: result.description
}
});
} });
And I'm getting empty body
You need to additionally process the multipart form-data. For example with multiparty:
const multiparty = require("multiparty");
router.post("/add", auth, async (req, res) => {
try {
const parse = function (req) {
return new Promise(function(resolve, reject) {
const form = new multiparty.Form()
form.parse(req, function(err, fields, files) {
!err ? resolve([fields, files]) : reject(err)
})
})
}
const [body] = await parse(req)
console.log(body)
} catch (err) {
console.log(err)
}
res.json("ok")
})

Resources