hi guys i try to upload file from react-nodejs to google clode storage ,
in client when i upload file and console log the file , it show the file array but when i try to send to server side , the array is empty
this is client
const [myFile, setmyFile] = useState("");
const onFileUpload = () => {
console.log(myFile);
Axios.post("http://localhost:10000/uploads", { myFile: myFile });
};
<div>
<h1>GeeksforGeeks</h1>
<h3>File Upload using React!</h3>
<div>
<input
type="file"
onChange={(event) => {
setmyFile(event.target.files[0]);
}}
/>
<button onClick={onFileUpload}>Upload!</button>
</div>
this is server
app.post("/uploads", async (req, res, next) => {
try {
const myFile = req.body.myFile;
console.log(myFile);
const imageUrl = await uploadImage(myFile);
res.status(200).json({
message: "Upload was successful",
data: imageUrl,
});
} catch (error) {
next(error);
}
});
can someone help me , why "myFile" return "{}"
Simple way of file uploading with react to node sever is.
On React here is how you want to handle things using axios
const data = new FormData();
data.append('media_file', file_input) // Note the file in quotes is the key that the server will use to retrive the input i.e **file_input** in this case
axios.post(url, data).then(res=>{
console.log(res)
}).catch(error=>{
console.log(error)
})
So now how you handle this on your nodejs is like this I will be using formidable as bodyParser very easy to use
const Formidable = require("formidable"); //Meant for body parsing
router.post('/api/file-upload', (req, res)=>{
const form = new Formidable.InconmingForm();
form.parse(req, (error, fields, files)=>{
const {media_file} = files
//Destructing 'media_file' remember name that we stated on the client
// it was 'media_file' now that is what I want to de-structure within files which comes
//with formidable
})
})
So now if you log media_file you will see all that you need about file then you can continue with your logic of uploading to google cloud
In client side you have to add your file to formData object, and set the Content-Type header to multipart/form-data.
Client side code -
const onFileUpload = () => {
console.log(myFile);
try {
const formData = new FormData()
formData.append('file', myFile)
Axios.post("http://localhost:10000/uploads", formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
} catch (error) {
console.error('Error while uploading image to server', error)
}
};
<div>
<h1> GeeksforGeeks </h1> <h3 > File Upload using React! </h3>
<div>
<input
type="file"
onChange={
(event) => {
setmyFile(event.target.files[0]);
}
}
/>
<button onClick={onFileUpload}> Upload! </button>
</div>
</div>
Server side:
You have to use multer or some other npm package to upload the files in the server side.
Once image is uploaded to google cloud storage, delete the file from local disk. finally block in below code is deleting the file from local disk once image is uploaded successfully or if there is any error in uploading the image.
const multer = require('multer')
const fs = require('fs')
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'public')
},
filename: function (req, file, cb) {
cb(null, Date.now() + '-' + file.originalname)
}
})
const upload = multer({ storage: storage }).single('file')
app.post("/uploads", async (req, res, next) => {
upload(req, res, async function (error) {
if (error instanceof multer.MulterError) {
return res.status(500).json(error)
} else if (error) {
return res.status(500).json(error)
}
const { filename } = req.file
var fileStream = fs.createReadStream(req.file.path)
try {
const options = {
filename
}
const imageUrl = await uploadImage(fileStream, options)
res.status(200).json({
message: "Upload was successful",
data: imageUrl,
});
} catch (error) {
next(error);
} finally {
fs.unlink(req.file.path, function (error) {
if (error) {
console.log('Error on deleting file from the path: ', req.file.path)
}
console.log('File deleted successfully from the disk')
})
}
})
});
As #Akanksha singh mentioned, you need to set the Content-Type header to multipart/form-data and use the formData object on the client side:
const onFileUpload = () => {
console.log(myFile);
try {
const formData = new FormData()
formData.append('file', myFile)
Axios.post("http://localhost:10000/uploads", formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
} catch (error) {
console.error('Error while uploading image to server', error)
}
};
<div>
<h1> GeeksforGeeks </h1> <h3 > File Upload using React! </h3>
<div>
<input
type="file"
onChange={
(event) => {
setmyFile(event.target.files[0]);
}
}
/>
<button onClick={onFileUpload}> Upload! </button>
</div>
</div>
After testing this, logging the req.body on the server side returns a buffer instead of an empty object.
In order to deal with this buffer, which contains the form data, I suggest you follow the steps mentioned in the official docs to deal with multipart/form-data in Cloud Functions
Here is the code sample from the docs:
/**
* Parses a 'multipart/form-data' upload request
*
* #param {Object} req Cloud Function request context.
* #param {Object} res Cloud Function response context.
*/
const path = require('path');
const os = require('os');
const fs = require('fs');
// Node.js doesn't have a built-in multipart/form-data parsing library.
// Instead, we can use the 'busboy' library from NPM to parse these requests.
const Busboy = require('busboy');
exports.uploadFile = (req, res) => {
if (req.method !== 'POST') {
// Return a "method not allowed" error
return res.status(405).end();
}
const busboy = new Busboy({headers: req.headers});
const tmpdir = os.tmpdir();
// This object will accumulate all the fields, keyed by their name
const fields = {};
// This object will accumulate all the uploaded files, keyed by their name.
const uploads = {};
// This code will process each non-file field in the form.
busboy.on('field', (fieldname, val) => {
/**
* TODO(developer): Process submitted field values here
*/
console.log(`Processed field ${fieldname}: ${val}.`);
fields[fieldname] = val;
});
const fileWrites = [];
// This code will process each file uploaded.
busboy.on('file', (fieldname, file, filename) => {
// Note: os.tmpdir() points to an in-memory file system on GCF
// Thus, any files in it must fit in the instance's memory.
console.log(`Processed file ${filename}`);
const filepath = path.join(tmpdir, filename);
uploads[fieldname] = filepath;
const writeStream = fs.createWriteStream(filepath);
file.pipe(writeStream);
// File was processed by Busboy; wait for it to be written.
// Note: GCF may not persist saved files across invocations.
// Persistent files must be kept in other locations
// (such as Cloud Storage buckets).
const promise = new Promise((resolve, reject) => {
file.on('end', () => {
writeStream.end();
});
writeStream.on('finish', resolve);
writeStream.on('error', reject);
});
fileWrites.push(promise);
});
// Triggered once all uploaded files are processed by Busboy.
// We still need to wait for the disk writes (saves) to complete.
busboy.on('finish', async () => {
await Promise.all(fileWrites);
/**
* TODO(developer): Process saved files here
*/
for (const file in uploads) {
fs.unlinkSync(uploads[file]);
}
res.send();
});
busboy.end(req.rawBody);
};
Related
I have an API and I'm uploading images using multer. I built backend that works perfectly fine and my images are uploaded and stored in my folder when I use postman, but when I try to upload images using frontend i dont know how to send them. I'm trying to have formData and append my files and then put that in my req.body. I need to have fields with name 'photos' but when i put my data and log req.body on backend i get data: [object FormData] and photos as an empty array. Also when i log req.files i get an empty array. My photos after extracting values from them look like this [File, File]
const handleHotelSubmit = async (e) => {
e.preventDefault();
const data = new FormData();
Object.values(photos).map((photo) => data.append("photos", photo));
setIsLoading(true);
try {
await axios.post(
`/hotels`,
{ ...info, data, featured, rooms },
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
} catch (err) {
setError(err.message);
}
setIsLoading(false);
};
My multer
const multerStorage = multer.memoryStorage();
const multerFilter = (req, file, cb) => {
if (file.mimetype.startsWith("image")) {
cb(null, true);
} else {
cb(new AppError("Not an image. Please upload only images", 400), false);
}
};
exports.resizeImage = catchAsync(async (req, res, next) => {
console.log(req.files);
if (!req.files) return next();
req.body.photos = [];
await Promise.all(
req.files.map(async (file) => {
const filename = `hotel-${uuidv4()}-${Date.now()}.jpeg`;
await sharp(file.buffer)
.resize(500, 500)
.toFormat("jpeg")
.jpeg({ quality: 90 })
.toFile(`public/img/hotels/${filename}`);
req.body.photos.push(filename);
})
);
next();
});
const upload = multer({
storage: multerStorage,
fileFilter: multerFilter,
});
exports.uploadHotelPhotos = upload.array("photos", 5);
Again code works with postman so clearly the problem is in the frontend
Since you specified in the headers that the request body will be multipart/form-data, then you need to put all other fields (info, featured, rooms) inside the formData data variable
I am trying to upload an image from my front end to the backend but it it doesn't send the image in the request
It says that the formdata is empty and it says that there's no image found, where is the problem and how can I fix this error?
Here is the code from the Frontend made in react:
const [userInfo, setuserInfo] = useState({
file:[],
filepreview:null,
});
const handleInputChange = (event) => {
setuserInfo({
...userInfo,
file:event.target.files[0],
filepreview:URL.createObjectURL(event.target.files[0]),
});
}
const [isSucces, setSuccess] = useState(null);
const submit = async () =>{
const formdata = new FormData();
formdata.append('avatar', userInfo.file);
console.log(formdata)
Axios.post("http://localhost:4000/imageupload", formdata,{
headers: { "Content-Type": "multipart/form-data" }
})
.then(res => { // then print response status
console.warn(res);
if(res.data.success === 1){
setSuccess("Image upload successfully");
}
})
}
The code of the Backend made in NodeJS:
const storage = multer.diskStorage({
destination: path.join(__dirname, './temp', 'uploads'),
filename: function (req, file, cb) {
// null as first argument means no error
cb(null, Date.now() + '-' + file.originalname )
}
})
app.post('/imageupload', async (req, res) => {
try {
// 'avatar' is the name of our file input field in the HTML form
let upload = multer({ storage: storage}).single('avatar');
upload(req, res, function(err) {
// req.file contains information of uploaded file
// req.body contains information of text fields
if (!req.file) {
return res.send('Please select an image to upload');
}
else if (err instanceof multer.MulterError) {
return res.send(err);
}
else if (err) {
return res.send(err);
}
const classifiedsadd = {
image: req.file.filename
};
res.send("ok")
});
}catch (err) {console.log(err)}
})
Edit:
Multer is essentially a nodejs router,i.e. a function that can be pipelined between your HTTP request and HTTP response.
I think that you should first make multer analyze your HTTP content and to actually populate the req.file before actually evaluate express parsers do their job.
const storage = multer.diskStorage({
destination: path.join(__dirname, './temp', 'uploads'),
filename: function (req, file, cb) {
// null as first argument means no error
cb(null, Date.now() + '-' + file.originalname )
}
})
let upload = multer({ storage: storage});
app.post('/imageupload', upload.single('avatar'), async (req, res) => {
try {
// 'avatar' is the name of our file input field in the HTML form
// req.file contains information of uploaded file
// req.body contains information of text fields
if (!req.file) {
return res.send('Please select an image to upload');
}
else if (err instanceof multer.MulterError) {
return res.send(err);
}
else if (err) {
return res.send(err);
}
const classifiedsadd = {
image: req.file.filename
};
res.send("ok")
}catch (err) {console.log(err)}
})
I am assuming that your upload code is working. Have you tried to read the HTTP request from your browser to see that the image has been correctly attached to the request?
Because probably the issue lies in the fact that you are not actually parsing the image.
const file = new File(userInfo.file, "avatar.png", {
type: 'image/png' // choose the appropriate
});
const formdata = new FormData();
formdata.append('avatar', file);
console.log(formdata)
I'm submitting a form on an Angular build front-end.
The form has both normal text input fields and a file upload function.
I POST both the text input fields to my NodeJS API as a JSON object "contact", as well as the file as a new FormData as such:
// 'contact' defined above as a JSON object
// 'profilePic' set from event.target.files[0] in a listener function
const profilePicData = new FormData();
profilePicData.append('file', profilePic);
return this.http
.post<ContactResponseData>('API_URL_HERE',
{ contact, profilePicData } ...
And then capture it from my API as such:
router.post("/", upload.single('file'),(req, res) => {
console.log("REQ: "+ req);
console.log("BODY: " + JSON.stringify(req.body));
console.log("FILE: " + req.file);
The req.file is "undefined", i.e. null, and my req.body has a "profilePicData" key value pair which is empty. I assume this is because the entire form gets submitted as JSON and not as multipart form data.
But I can't google anything helpful around how to send both JSON and multipart to my API as one POST request so that both req.body and req.file pick up the right information. I guess understanding the theory and best practices behind what's going here is what I'm after. Should I have two POST urls, one for JSON and one for file? Or should I be submitting my JSON as multipart as well (how do I do that in Angular)? Any help is appreciated.
You will have to send everything as multipart by adding fields to an instance of FormData and send it as the payload.
const form = new FormData();
form.append('file', profilePic);
form.append('contact', contact);
...
return this.http.post<ContactResponseData>('API_URL_HERE', form, ...)
I used the below method in React
Below is how I created input
<form className={styles.root} noValidate autoComplete="off">
<input
name="avatar_image" // name of input field or fieldName simply
enctype="multipart/form-data"
type="file"
onChange={(event) => {
// console logging selected file from menu
console.log( event.target.files[0] ) // gives first file
// setState method with event.target.files[0] as argument
this.setState(prev => ({...prev, user_image: event.target.files[0]}))
}}
/>
</form>
Below is how I made requests to backend
const formData = new FormData()
formData.append('user_name', this.state.user_name)
formData.append('phone_number', this.state.phone_number)
// now below avatar_image is the fieldName of the image, then comes the file to upload, and the file name in the end
formData.append('avatar_image', this.state.user_image, this.state.user_image.name)
axios.post(utils.baseUrl + '/avatar-uploads/avatar-image-upload', formData, {
onUploadProgress: progressEvent => {
console.log( 'upload progress: ' + Math.round((progressEvent.loaded / progressEvent.total)*100) + '%' )
}
})
.then(function (response) {
// further code
})
.catch(function (error) {
console.log(error);
});
Below is how I handled the backned with multer, including dealing with payload
const image_storage = multer.diskStorage({
destination: path.join(__dirname , '../../assets/images/uploads/avatar_image'),
filename: function(req, file, cb){
cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
}
});
// Init Upload
const user_avatar_image_upload = multer({
storage: image_storage,
limits:{fileSize: 2000000}, // 1 mb
fileFilter: function(req, file, cb){
checkFileTypeForUserAvatar(file, cb);
}
}).single('avatar_image'); // this is the fieldName that will be dealt
// Check File Type
function checkFileTypeForUserAvatar(file, cb){
// Allowed ext
let filetypes = /jpeg|jpg|png|gif/;
// Check ext
let extname = filetypes.test(path.extname(file.originalname).toLowerCase());
// Check mime
let mimetype = filetypes.test(file.mimetype);
if(mimetype && extname){
return cb(null,true);
} else {
cb('Error: jpeg, jpg, png, gif Images Only!');
}
}
// router.post('/protected-avatar-image-upload', passport.authenticate('jwt', { session: false }), (req, res, next) => {
router.post('/avatar-image-upload', (req, res, next) => {
console.log(req.body) // here the req.body will turn out {}
user_avatar_image_upload(req, res, (err) => {
if(err){
console.log(err)
} else {
if(req.file == undefined){
res.status(404).json({ success: false, msg: 'File is undefined!',file: `uploads/${req.file.filename}`})
} else {
console.log( req.body.user_name ) // here req.body.user_name and others will work
// further code
res.status(200).json({ success: true, msg: 'File Uploaded!',file: `uploads/${req.file.filename}`})
}
}
})
})
Hope this helps how to upload a file using multer, with additional payload being passed so that it can be utilized as well with creating database entries or anything else.
React Code for file upload:
onChangeHandler=event=>{
this.setState({
selectedFile: event.target.files[0],
loaded: 0,
})
console.log(event.target.files[0])
}
onClickHandler = () => {
const data = new FormData()
data.append('file', this.state.selectedFile)
console.log(this.state.selectedFile)
axios.post(`${config.server}/upload`, data,{})
.then(res => { // then print response status
console.log(res.statusText)
});
}
<div>
<input type="file" name="file" onChange={this.onChangeHandler}/>
<button type="button" class="btn btn-success btn-block" onClick={this.onClickHandler}>Upload</button>
</div>
File Handling code of Node:
var multer=require("multer");
var cors = require('cors');
app.use(cors())
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'public')
},
filename: function (req, file, cb) {
cb(null, Date.now() + '-' +file.originalname )
}
})
var upload = multer({ storage: storage }).single('file')
app.post('/upload',function(req, res) {
console.log(req.file)
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)
})
});
The onChangehandler is showing the file in react on console.log() but on submitting the data using the button Upload which uses the onClickHandler function. The response I get from the server is 500 and the directory with the name public where it should store the file is not getting created. What is going wrong if someone can help me. I am new to file handling in react and node. I am using the following link as reference. It is not necessary to store the file. All I need to do is process the file and read the contents inside the file. If there is some other way of doing instead of storing it.
Thanks for your help.
The problem has been solved.
I didn't have the folder public which is my destination for saving the file. There is no changes needed in the code. The destination folder needs to be present. Multer cannot create the folder if not present.
I'm trying to upload an array of photos to a server but the req.files array always shows up empty when it gets there.
req.body displays the array as expected.
The images are added through a Dropzone component. (I've tried switching this for a standard input but they both seem to pass files the same way)
<Dropzone
onDrop={onDrop}
onSubmit={uploadPhotos}
maxFiles={20}
inputContent="Drop 20 Images"
inputWithFilesContent={files => `${20 - files.length} more`}
/>
The images are applied to FormData with the name image files are appended before being sent via an Axios POST request with multipart/form-data headers set.
export const uploadPhotos = (files) => {
const formData = new FormData();
for (let i = 0; i < files.length; i += 1) {
formData.append("image[]", files[i]);
}
const config = {
headers: {
'Content-Type': `multipart/form-data`
}
}
return async (dispatch, getState) => {
try {
const response = await axios.post('/api/kite/upload',
formData, config)
.then(function(response) {
console.log(response.data);
dispatch({
type: ORDER_CHANGE,
payload: response.data
});
});
} catch (err) {
console.log(err);
} finally {
console.log('done');
}
}
}
once passed to the server only req.body seems to contain any data and req.files is empty despite using Multer middleware as the second parameter. Once passed to files.map() items are undefined undefined, presumably because req.files is an empty array.
var multer = require('multer');
var AWS = require('aws-sdk');
AWS.config.setPromisesDependency(bluebird);
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'upload')
},
filename: (req, file, cb) => {
cb(null, file.fieldname + '-' + Date.now())
}
});
const upload = multer({
storage: storage
}).array('image');
router.post('/upload', upload, function (req, res) {
const file = req.files;
let s3bucket = new AWS.S3({
accessKeyId: IAM_USER_KEY,
secretAccessKey: IAM_USER_SECRET,
Bucket: 'BUCKETNAME'
});
s3bucket.createBucket(function () {
let Bucket_Path = 'https://console.aws.amazon.com/s3/buckets/BUCKETNAME?region=eu-west-1';
var ResponseData = [];
file.map((item) => {
// item.x are all undefined
var fileStream = fs.createReadStream(filePath);
var params = {
Bucket: Bucket_Path,
Key: item.originalname,
Body: item.buffer,
ACL: 'public-read'
};
s3bucket.upload(params, function (err, data) {
if (err) {
res.json({ "error": true, "Message": err});
} else{
ResponseData.push(data);
if(ResponseData.length == file.length){
res.json({ "error": false, "Message": "File Uploaded SuceesFully", Data: ResponseData});
}
}
});
});
});
});
My end goal is to pass the images to an Amazon S3 bucket. I don't think it impacts this since there is no data to send but I've included it incase it has somehow affecting this.
I've been through lots of other similar Stack Overflow questions and medium post and the main three resolutions to this issue seem to be included in the flow above.
Append file name to items of FormData array
Set POST request headers
Include Multer middleware in express parameter
Can anyone help me figure out why req.files is an empty array?
It might be that Dropzone isn't processing the files. Try adding this to the uploadPhotos function:
const acceptedFiles = myDropzone.getAcceptedFiles() // "myDropzone" is just the Dropzone instance
for (let i = 0; i < acceptedFiles.length; i++) {
myDropzone.processFile(acceptedFiles[i])
}