I want to upload the file from front end developed in React and get that uploaded file inside Express js.
Below is the code snippet of React side when I am uploading the file :
handleUploadFile(ev) {
ev.preventDefault();
var a6 = "File";
const data = new FormData();
data.append('file', this.uploadInput.files[0]);
data.append('fileName', a6);
fetch('http://localhost:4000/api/addDcuments/upload', {
method: 'POST',
body: {data},
json: true,
headers: { "Authorization": cookie.load('userToken') }
}).then((response) => {
response.json().then((body) => {
this.setState({ imageURL: `http://localhost:4000/${body.file}` });
});
});
}
In above code, I have taken the form and called handleUploadFile function on it's onSubmit event.
Now, below is my backend express js code on which I am getting the uploaded file:
export function uploadDocument(req, res, next) {
console.log(JSON.stringify(req.body));
let imageFile = req.files.file;
var ext = path.extname(imageFile.name)
if(imageFile.mimetype == "application/pdf" ||imageFile.mimetype == "application/octet-stream"){
imageFile.mv('D:/React Workspace/React-videos-example/file_path/'+req.body.filename+ext, function(err) {
if (err) {
return res.status(500).send(err);
}
res.json({file: `public/${req.body.filename}.pdf`});
});
}
}
In above code, when I am trying print the req.body, it is returning "{}" as well as I am getting one error:
TypeError: Cannot read property 'file' of undefined.
So, my backend function has been called, but not able to getting that uploaded file. So can anyone have any solution or any reference link for this issue?
When using formdata, you can't use type json in your POST, it needs to be:
contentType: 'multipart/form-data'
instead of "json: true".
Related
I have problem uploading file using POST request in Node.js. I have to use request module to accomplish that (no external npms). Server needs it to be multipart request with the file field containing file's data. What seems to be easy it's pretty hard to do in Node.js without using any external module.
I've tried using this example but without success:
request.post({
uri: url,
method: 'POST',
multipart: [{
body: '<FILE_DATA>'
}]
}, function (err, resp, body) {
if (err) {
console.log('Error!');
} else {
console.log('URL: ' + body);
}
});
Looks like you're already using request module.
in this case all you need to post multipart/form-data is to use its form feature:
var req = request.post(url, function (err, resp, body) {
if (err) {
console.log('Error!');
} else {
console.log('URL: ' + body);
}
});
var form = req.form();
form.append('file', '<FILE_DATA>', {
filename: 'myfile.txt',
contentType: 'text/plain'
});
but if you want to post some existing file from your file system, then you may simply pass it as a readable stream:
form.append('file', fs.createReadStream(filepath));
request will extract all related metadata by itself.
For more information on posting multipart/form-data see node-form-data module, which is internally used by request.
An undocumented feature of the formData field that request implements is the ability to pass options to the form-data module it uses:
request({
url: 'http://example.com',
method: 'POST',
formData: {
'regularField': 'someValue',
'regularFile': someFileStream,
'customBufferFile': {
value: fileBufferData,
options: {
filename: 'myfile.bin'
}
}
}
}, handleResponse);
This is useful if you need to avoid calling requestObj.form() but need to upload a buffer as a file. The form-data module also accepts contentType (the MIME type) and knownLength options.
This change was added in October 2014 (so 2 months after this question was asked), so it should be safe to use now (in 2017+). This equates to version v2.46.0 or above of request.
Leonid Beschastny's answer works but I also had to convert ArrayBuffer to Buffer that is used in the Node's request module. After uploading file to the server I had it in the same format that comes from the HTML5 FileAPI (I'm using Meteor). Full code below - maybe it will be helpful for others.
function toBuffer(ab) {
var buffer = new Buffer(ab.byteLength);
var view = new Uint8Array(ab);
for (var i = 0; i < buffer.length; ++i) {
buffer[i] = view[i];
}
return buffer;
}
var req = request.post(url, function (err, resp, body) {
if (err) {
console.log('Error!');
} else {
console.log('URL: ' + body);
}
});
var form = req.form();
form.append('file', toBuffer(file.data), {
filename: file.name,
contentType: file.type
});
You can also use the "custom options" support from the request library. This format allows you to create a multi-part form upload, but with a combined entry for both the file and extra form information, like filename or content-type. I have found that some libraries expect to receive file uploads using this format, specifically libraries like multer.
This approach is officially documented in the forms section of the request docs - https://github.com/request/request#forms
//toUpload is the name of the input file: <input type="file" name="toUpload">
let fileToUpload = req.file;
let formData = {
toUpload: {
value: fs.createReadStream(path.join(__dirname, '..', '..','upload', fileToUpload.filename)),
options: {
filename: fileToUpload.originalname,
contentType: fileToUpload.mimeType
}
}
};
let options = {
url: url,
method: 'POST',
formData: formData
}
request(options, function (err, resp, body) {
if (err)
cb(err);
if (!err && resp.statusCode == 200) {
cb(null, body);
}
});
I did it like this:
// Open file as a readable stream
const fileStream = fs.createReadStream('./my-file.ext');
const form = new FormData();
// Pass file stream directly to form
form.append('my file', fileStream, 'my-file.ext');
const remoteReq = request({
method: 'POST',
uri: 'http://host.com/api/upload',
headers: {
'Authorization': 'Bearer ' + req.query.token,
'Content-Type': req.headers['content-type'] || 'multipart/form-data;'
}
})
req.pipe(remoteReq);
remoteReq.pipe(res);
I want to initiate a download of a server generated image right after it has been generated. I am using fetch to post user input data (no HTML forms). The image does get generated on the server but I can't figure out how to download it to the client afterwards.
NodeJS Code:
app.post('/download', (req, res) => {
const HTML = ArrayToHTML(req.body)
nodeHtmlToImage({
output: './image.png',
html: HTML,
transparent: true
})
res.download('./image.png')
})
Client Side Javascript Code:
toServer = (data) => {
fetch("/download", {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json"
}
})
}
What I have tried
Trying to download it after nodeHtmlToImage has generated the image. This doesn't work because I think the image is not generated yet. Is there a way to make an asynchronous function synchronous?
Tacking on a .then to nodeHtmlToImage and trying to use res.download() in there but nothing happens. No error at client or server.
Solved the problem.
The nodeHtmltoImage Library can return a buffer of the image. This buffer was sent back as a response to the client. I used download.js on the client side to download the returned buffer as an image.
The final code:
Client Side Javascript Code:
toServer = (data) => {
fetch("/download", {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json"
}
}).then(res => {
return res.blob()
}).then(blob => download(blob, data.tableName))
}
NodeJS Code:
app.post('/download', async (req, res) => {
const HTML = ArrayToHTML(req.body)
console.log("Generating")
const image = await nodeHtmlToImage({
html: HTML,
transparent: true
})
res.writeHead(200, { 'Content-Type': 'image/png' });
res.end(image, 'binary');
})
For this I'm using express.js, react, with the help of form-data.
No matter what I try, when I send a file from the frontend to the server, the server always says it's undefined.
In react, the code is as follows:
onSubmit = () => {
const url = '../api/products/file';
const formData = new FormData();
formData.append('file', this.state.file)
const config = {
headers: {
'content-type': 'multipart/form-data'
}
}
console.log(formData);
axios.post(url, formData, config)
.then(res => {
console.log(res.statusText)
})
}
I know the computer has the file because this.state.file returns all the relevant info on the file.
For reference, in the console.log(formData) line, I receive this in the console.
In express, the code is as follows:
router.post('/file', (req,res) => {
console.log("FILE");
console.log(req.file);
console.log(req.body);
upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
return res.status(500).json(err)
} else if (err) {
return res.status(500).json(err)
}
return res.status(200).send(req.file)
})
});
My problem here is that my req.file returns 'undefined' whereas my req.body returns {} which indicates that the file isn't even making its way to the server. I'm thinking that either the file isn't going to the form-data properly through appending or it's somehow getting lost on the way to the server which I think is highly unlikely. I've looked over every line of the request of Axios and there's no details on the files within.
I have an array of the object, each object contains some properties of type string and a file.
This is how my array looks like:
const args = [
{
name: 'presentation',
price: 9.99,
tags: 'invest, finance, business',
file: File,
thumbnail: File
},
{
name: 'presentation2',
price: 20.99,
tags: 'invest, finance, business',
file: File,
thumbnail: File
}
]
const headers = {
headers: {
Authorization: `Bearer ${token}`
}
};
My goal is to send this whole array of the object to the Express Server. there I will save information to the Mongo and upload the file to S3.
But Currently, I am unable to get the file stream on the server when I send this in a POST request,
even the whole req.body is an empty object when I console.log it
axios.post(`${slidesRoute}/createSlides`, args, headers);
Alternatively, I tried to put everything into FormData.
for example:
let formData = new FormData();
selectedFiles.forEach((selectedFile, i) => {
formData.append(`name_${i}`, selectedFile.name);
formData.append(`price_${i}`, selectedFile.price);
formData.append(`tags_${i}`, selectedFile.tags);
formData.append(`file_${i}`, selectedFile.file);
formData.append(`thumbnail_${i}`, selectedFile.thumbnail);
});
axios.post(`${slidesRoute}/createSlides`, formData, headers);
Then In the backend. I am unable to catch these files using multer. Because the filenames in form data are being generated dynamically like
file_1, file_2 file_3,...
But multer expects the exact filename to be passed
for example multer().array('file')
so in my second approach I am unable to catch files using Multer
You can't include file inside JSON. File is not JSON-serializable. Ideally you should have a separate endpoint to accept your files, send them with content-type: multipart/form-data, receive unique id for that file stored on the server, then send your JSON with ids instead of files.
As a chaper alternative you can Base64 encode your files into a string, insert this string in your request, and then decode it back on the receiving side
FormData key values are array by default
in your code Just change
formData.append(`file_${i}`, selectedFile.file);
to
formData.append(`file`, selectedFile.file);
You should be able to receive the file array in server side using multer().array('file')
Sample below demonstrating FormData key values array behavior
const formData = new FormData();
formData.append('key1', 'value1');
formData.append('key1', 'value2');
formData.append('key2', 'value1');
formData.forEach(function(value, key){
console.log(key, value);
});
I use FormData to send a number of files - and then on the node.js server I use Formidable.
I've pasted some partial code below that may help - sorry it's not a smaller sample.
Hope it helps
On the browser side I have:
let form_data = new FormData()
form_data.append('upload_data', file)
$.ajax({
url: '/file/upload' + remote_path + '/' + filename,
type: 'POST',
data: form_data,
processData: false,
contentType: false,
success: (res) => {
...
On the node.js side (apologies for the length..)I have:
const fs = require('fs')
const formidable = require('formidable')
const path = require('path')
...
app.post('/file/upload/*', (req, res) => {
let filename = req.params[0]
let media = path.basename(filename)
let folder = filename.substring(0, filename.length - media.length)
let form = new formidable.IncomingForm()
// form.encoding = 'utf-8'
form.multiples = true
form.uploadDir = path.join(media_folder, folder)
form.keepExtensions = true
form.parse(req, (err, fields, files) => {
if (err) {
fail(res, 'failed to upload')
} else {
success(res)
}
})
form.on('fileBegin', (name, file) => {
const [fileName, fileExt] = file.name.split('.')
file.path = path.join(form.uploadDir, `${fileName}_${new Date().getTime()}.${fileExt}`)
})
form.on('file', (field, file) => {
fs.rename(file.path, path.join(form.uploadDir, file.name), (err) => {
if (!res.headersSent) { // Fix since first response is received
fail(res, 'an error has occured with form upload' + err)
}
})
})
form.on('error', (err) => {
fail(res, 'an error has occured with form upload' + err)
})
form.on('aborted', (err) => {
fail(res, 'Upload cancelled by browser')
})
})
I want to upload a jpg to a link with axios but failed.
I doubt that is there sth wrong with my form data, I dont know how to check that.
I do it in this way:
let form = new FormData();
form.append('screenshot',fs.createReadStream(`xxx.jpg`));
screenshot is the key while the next one is the jpg that I want to post.
Here is my post method:
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
yield axios.post(url,form,config).then....catch....
I have successfully upload the jpg through postman:
How can I do the same thing with NodeJS in VScode?
The error message is very long but main pt. should be response data: { error: 'Multipart: Boundary not found' }.
Here is some content in sever:
const upload = multer({
//dest: 'screenshots',
limits: {
fileSize: 2000000 //number in bytes
},
fileFilter(req, file, cb) {
if (!file.originalname.match(/\.(png|jpg|jpeg)$/)) {
return cb(new Error('File must be of png/jpg/jpeg type.'))
}
cb(undefined, true)
}
router.post('/upload/:id/screenshot', findCasebyID, upload.single('screenshot'), async (req, res) => {
try {
req.case.screenshot = req.file.buffer
await req.case.save()
res.send()
} catch (e) {
res.status(409).send(e)
}...
I managed to solve it with this:
const config = { headers: { 'Content-Type': `multipart/form-data; boundary=${form._boundary}`, } };