file upload with axioso is showing undefined in multer - node.js

I am trying to upload file using react and axios to node backend using multer.
when i use form as :
<form action = '/article/add' method="POST" className = {adminStyle.add_article_form} enctype="multipart/form-data">
<input type='text' className={adminStyle.add_article_input_title} autoComplete = 'off' name='title' placeholder='Title' value = {state.title} onChange={changeHandler}/>
<select name='category' value = {state.category} onChange={changeHandler}>
{options}
</select>
<input type="file" name='thumbnail' className={adminStyle.add_article_input_file} onChange={changeFileHandler}/>
<textarea type = 'text' name='body' className={adminStyle.add_article_textarea} rows='30' cols='100' value = {state.body} onChange={changeHandler}></textarea>
<button type='submit'>Submit</button>
</form>
it works fine but when using axios the file is undefined:
I used axios as:
const [state,setState] = useState({
title: '',
category: 'Choose one',
body: '',
thumbnail: ''
})
const changeFileHandler = (e) => {
setState({ thumbnail: e.target.files[0] })
}
const postArticle = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('thumbnail', state.thumbnail)
axios.post('/article/add', state,config).then(data => console.log(data))
}
<form onSubmit={postArticle} className = {adminStyle.add_article_form} enctype="multipart/form-data">
<input type='text' className={adminStyle.add_article_input_title} autoComplete = 'off' name='title' placeholder='Title' value = {state.title} onChange={changeHandler}/>
<select name='category' value = {state.category} onChange={changeHandler}>
{options}
</select>
<input type="file" name='thumbnail' className={adminStyle.add_article_input_file} onChange={changeFileHandler}/>
<textarea type = 'text' name='body' className={adminStyle.add_article_textarea} rows='30' cols='100' value = {state.body} onChange={changeHandler}></textarea>
<button type='submit'>Submit</button>
</form>
My backend is as follow:
const storage = multer.diskStorage({
destination: path.join(__dirname, '..', '../public/uploads'),
filename: function(req, file, cb){
cb(null,file.fieldname + '-' + Date.now() + path.extname(file.originalname));
}
});
function checkFileType(file, cb){
// Allowed extension name
const filetypes = /jpeg|jpg|png|gif/;
// Check ext
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = filetypes.test(file.mimetype);
if(mimetype && extname){
return cb(null,true);
} else {
cb('Error: Images Only!');
}
}
const upload = multer({
storage: storage,
limits:{fileSize: 1000000},
fileFilter: function(req, file, cb){
checkFileType(file, cb);
}
}).single('thumbnail')
router.post('/add',(req, res)=>{
upload(req,res, (err) => {
console.log(req.file)
})
});
can anyone help me with this? I am really stuck. I need to upload the image in the backend.

You are passing state and not formData as the body of the request. You should change this line:
axios.post('/article/add', state,config).then(data => console.log(data))
with this line:
axios.post('/article/add', formData ,config).then(data => console.log(data))

Related

Uploading file with multer AND get data from ajax

I have a problem to using multer when i have some data to pass with ajax at the same time (from the front side) to the server side (with nodejs).
I think there is a conflict between the two methods. Here an example :
My HTML :
<form action="api/stats" enctype="multipart/form-data" method="post">
<div class="form-group">
<div class="mb-3">
<label for="inputBookName" class="form-label">Name</label>
<input type="text" class="form-control" id="inputBookName">
</div>
<div class="mb-3">
<label for="inputBookCategory" class="form-label">Categories</label>
<select id="inputBookCategory" class="form-select" multiple="multiple"></select>
</div>
<input type="file" name="uploaded_file">
<input type="submit" value="Send!">
</div>
</form>
My client side : (I have to pass "bookInfo" object with some data that I get from select2 (categoryName). When i click on the submit button :
const dataSelectedCatagories = $('#inputBookCategory').select2('data');
const categories = [];
let bookInfo;
for (let j = 0; j < dataSelectedCatagories.length; j++) {
const category = dataSelectedCatagories[j];
const categoryId = category._resultId;
const categoryName = category.text;
categories.push({
id: categoryId,
name: categoryName
});
}
bookInfo = {
bookid: Date.now(),
bookname: bookName,
name: categoryName
};
$.ajax({
type:"POST",
dataType:"json",
contentType: "application/json",
data:JSON.stringify(bookInfo), //Send some datas to front
url:"api/stats"
}).done(function(response){
console.log("Response of update: ",response)
}).fail(function(xhr, textStatus, errorThrown){
console.log("ERROR: ",xhr.responseText)
return xhr.responseText;
});
My Server side
const multer = require('multer');
const path = require('path');
const image_storage = multer.diskStorage({
destination: path.join(__dirname , '../../public/uploads'),
filename: function(req, file, cb){
cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
}
});
const fileUpload = multer({
storage: image_storage,
limits:{fileSize: 2000000} // 1 mb
}).single('uploaded_file');
uploadBookMulter = async (req, res) => {
console.log('body' , req.body) //get the datas from the front
await fileUpload(req, res, (err) => {
console.log('file : ', req.file) //here the req.file will turn out empty
})
};
The upload works very well. As well as the data feedback in ajax. The problem is when I plug the two together, the req.file is empty...
Isn't there a solution to have the data of the ajax and that of the multer side?

multer unexpected field error despite having matching fields

i have an uncontrolled form in react in which i'm supposed to upload a video and multiple images, i keep getting the "unexpected field" error despite matching the names in my data object and in the multer upload function
the react code looks like this
function handleSubmit(e) {
e.preventDefault();
let newObject = {
name: e.target["name"].value,
tags: e.target["tags"].value,
address: e.target["address"].value,
images: e.target["images"].files,
video: e.target["video"].files[0],
description: e.target["description"].value
}
axios.post("/api/uploadInfo", newObject, { headers: { "Content-Type": "multipart/form-data" } }).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
}
function FormGroup({ name, id, type, text, required, accept, multiple }) {
return (
<div className="form-group my-3 mx-auto">
<label className="form-label m-0 text-main text-start fs-4" htmlFor={id}>{text}</label>
<input className="form-control ms-1" required={required} type={type} name={name} id={id} accept={accept ? accept : ""} multiple={multiple ? multiple : false}/>
</div>
)
}
<form onSubmit={handleSubmit} className="">
<FormGroup name="name" id="name" type="text" text={text} required={true} />
<FormGroup name="tags" id="tags" type="text" text={text} required={true} />
<FormGroup name="address" id="address" type="text" text={text} required={true} />
<FormGroup name="images" id="images" type="file" text={text} accept="image/*" required={false} multiple={true} />
<FormGroup name="video" id="video" type="file" text={text} accept="video/mp4,video/x-m4v,video/*" required={false} />
<div className="my-5 mx-auto w-50">
<label className="form-label text-main fs-3" htmlFor="msg">{text}</label>
<textarea className="form-control" required="required" name="description" id="description" rows="8" ></textarea>
</div>
<button className="btn btn-lg w-25 mt-5">{text}</button>
</form>
on my backend the multer and router code look like this
const multer = require("multer");
const { v4: uuidv4 } = require('uuid');
const fs = require("fs");
const router = express.Router();
const DIR = './uploads/videos/';
const storage = multer.diskStorage({
destination: (req, file, cb) => {
fs.mkdirSync(DIR, { recursive: true });
cb(null, DIR);
},
filename: (req, file, cb) => {
const fileName = file.originalname.toLowerCase().split(' ').join('-');
cb(null, uuidv4() + '-' + fileName)
}
});
let vidUpload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (file.mimetype == "video/mp4") {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('Only video format allowed!'));
}
}
});
const imagesDIR = './uploads/images/';
const imagesStorage = multer.diskStorage({
destination: (req, file, cb) => {
fs.mkdirSync(imagesDIR, { recursive: true });
cb(null, imagesDIR);
},
filename: (req, file, cb) => {
const fileName = file.originalname.toLowerCase().split(' ').join('-');
cb(null, uuidv4() + '-' + fileName)
}
});
let imagesUpload = multer({
storage: imagesStorage,
fileFilter: (req, file, cb) => {
if (file.mimetype == "image/png" || file.mimetype == "image/jpg" || file.mimetype == "image/jpeg") {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('Only .png, .jpg and .jpeg format allowed!'));
}
}
});
router.post("/newInfo", imagesUpload.array("images",8), vidUpload.single('video'), (req, res) => {
console.log(req.body);
res.sendStatus(200);
})
i've faced no problems with uploading the video, but it seems that the images field keeps returning "unexpected field error", i tested it by trying to remove the images upload MW and then the video uploads fine on its own, but when i removed the video MW the images just keep giving me this error..what am i doing wrong?
Man we really learn the most when we fix our issues ourselves..as commented above i traced the issue and found that i can't store multiple files in list/array and send that to multer..instead of using a normal js object use a FormData object where you can loop over each file in the file list and append it under the same key..i don't understand how they don't override each other but it works alright, in the code in the post just replace the object code with this and you're done
let formData = new FormData();
formData.append('name', e.target["name"].value);
formData.append('tags', e.target["tags"].value);
formData.append('address', e.target["address"].value);
formData.append('description', e.target["description"].value);
formData.append('video', e.target["video"].files[0]);
let imgFiles = e.target["images"].files;
for (let i = 0; i < imgFiles.length; i++) {
formData.append('images', imgFiles[i]);
}
and send the formData in the body of the post request

how to solve this Cannot POST error in MERN?

I am inserting two images along with the form data into MongoDB database.
While both images are stored in my pc folder but all form data isn't uploading in the database.
error
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /</pre>
</body>
</html>
in my console. Please help me how to solve that.
I have tried some previously asked question in stackOF.
app.js
const express = require("express")
const app = express()
const cors = require('cors');
const env = require("dotenv")
const port = 5000
env.config({path: "../server/config.env"})
require("../server/db/conn")
app.use(cors());
app.use(express.json())
app.use(require("../server/router/auth"))
app.listen(port, (err) => {
if (err) {
return console.error(err);
}
return console.log(`server is listening on ${port}`);
});
module.exports = "conn"
register.js(frontend)
const Register = () => {
const [newUser, setNewUser] = useState({
school: "",
address: "",
photo: "",
photoone: ""
});
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("school", newUser.school);
formData.append("photo", newUser.photo);
formData.append("photoone", newUser.photoone)
formData.append("address", newUser.address);
axios({
method: "post",
url: "/teacher",
data: formData,
headers: { "Content-Type": "multipart/form-data" },
})
.then((response) => {
console.log(response)
}).then((data) => {
console.log(data)
}).catch((error) => {
if (error.response) {
console.log(error.response.data)
}
})
};
const handleChange = (e) => {
setNewUser({ ...newUser, [e.target.name]: e.target.value });
};
const handlePhoto = (e) => {
setNewUser({ ...newUser, photo: e.target.files[0] });
};
const handlePhotoone = (e) => {
setNewUser({ ...newUser, photoone: e.target.files[0] });
};
return (
<>
<div className="container main">
<div className="row">
<div className="col-sm-6 col-md-6 col-lg-6">
<form onSubmit={handleSubmit} encType="multipart/form-data">
<div class="mb-3">
<label class="form-label">
Your school
</label>
<input
type="text"
class="form-control"
id="exampleInputPassword1"
id="school"
name="school"
value={newUser.school}
onChange={handleChange}
/>
</div>
<div class="input-group mb-3">
<input
type="file"
id="pic"
accept=".png, .jpg, .jpeg"
name="photo"
onChange={handlePhoto} type="file" class="form-control" id="inputGroupFile02" />
</div>
<div class="input-group mb-3">
<input
type="file"
id="pic"
placeholder="second photo"
accept=".png, .jpg, .jpeg"
name="photoone"
onChange={handlePhotoone} type="file" class="form-control" id="inputGroupFile01" />
</div>
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">
your address
</label>
<input
type="text"
id="address"
name="address"
value={newUser.address}
onChange={handleChange}
class="form-control"
aria-describedby="emailHelp"
/>
</div>
<button
value="register"
type="submit"
class="btn btn-primary"
>
Submit
</button>
</form>
</div>
</div>
</div>
</>
);
};
auth.js(backend)
const mongoose = require("mongoose")
const express = require("express")
const router = express()
require("../db/conn")
const User = require("../model/userSchema")
const Teacher = require("../model/userSchemaTeacher")
const multer = require('multer');
let path = require('path');
let fs = require("fs-extra");
const storage = multer.diskStorage({
destination: function (req, file, cb) {
let schoolname = req.body.school;
let path = `C:/Users/kumar/Desktop/mern/server/images/${schoolname}`;
fs.mkdirsSync(path);
cb(null, path);
// cb(null, 'images');
},
filename: function (req, file, cb) {
cb(null, 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, fileFilter });
router.route('/teacher').post(upload.fields([{
name: "photo", maxCount: 1
}, {
name: "photoone", maxCount: 1
}
])), (req, res) => {
const school = req.body.school;
const photo = req.file.filename
const photoone = req.file.filename
const address = req.body.address;
const newUserData = {
school,
photo,
photoone,
address,
}
const newUser = new Teacher(newUserData);
newUser.save()
.then(() => res.json('User Added'))
.catch((err) => {
console.log(err);
});
}
Please see how to solve that?
The route you are trying to POST your form data is not defined please set your route path like this:
router.post('/teacher',upload.fields([{
name: "photo", maxCount: 1
}, {
name: "photoone", maxCount: 1
}
]), (req, res) => {
const school = req.body.school;
const photo = req.files['photo'][0]
const photoone = req.files['photoone'][0]
const address = req.body.address;
const newUserData = {
school,
photo,
photoone,
address,
}
const newUser = new Teacher(newUserData);
newUser.save()
.then(() => res.json('User Added'))
.catch((err) => {
console.log(err);
});
})
...

Multer not able to get the filename

I'm not able to get the filename or path from Multer. This is what I've done so far:
uploadcsv.js
const fileUpload = multer({
limits: 500000,
storage:multer.diskStorage({
destination: (req, file, cb) =>{
cb(null,'upload/csv')
},
filename: (req, file, cb) =>{
const ext = MIME_TYPE_MAP[file.mimetype]
cb(null, uuid + '.' + ext)
},
fileFilter: (req, file, cb) =>{
const isValid = !!MIME_TYPE_MAP[file.mimetype]
let error = isValid ? null : new Error('Invalid mime type')
cb(error, isValid)
}
})
})
Then the route api:
router.post('/contact/importcontact', JWTAuthenticationToken, fileUpload.single('csv'), async (req, res) => {
console.log(req.body)
console.log(req.file.filename)
const csvFilePath = req.file.filename
const stream = fs.createReadStream(csvfile);
const account= await Account.findOne({ acctid: { "$eq": req.body.acctid } })
try {
if (req.file == undefined)
return res.status(400).send({ msg: 'No files were uploaded.' });
csvtojson()
.fromFile(csvFilePath)
.then((jsonObj) => {
console.log(jsonObj);
})
// Async / await usage
const jsonArray = await csvtojson().fromFile(csvFilePath);
res.json({ success: "Uploaded Successfully", status: 200 })
} catch (error) {
res.json({ message: error })
}
})
Lastly, the react importcustomer.js
const handleCSVChange = (e) => {
console.log(e.target.files[0])
setCsvData(e.target.files[0])
setUploadButtonData(e.target.files[0].name)
}
const uploadCSVData = async (e) => {
setLoading(true)
const formData = new FormData();
formData.append("csv", csvData);
console.log(formData)
e.preventDefault()
const response = await Axios.post(process.env.REACT_APP_FETCH_URL + '/api/contact/importcontact', { formData: formData, acctid: lsData.acctid}, { withCredentials: true })
if (response.data.statusCode === "409") {
setMessage(response.data.msg)
setLoading(false)
}
else if (response.data.statusCode === "200") {
setLoading(false)
//history.push('/sources')
}
}
return (
<div className="col-12 grid-margin stretch-card">
<div className="card">
<div className="card-body">
<h4 className="card-title">Import Customer Data From CSV</h4>
<form className="forms-sample" enctype="multipart/form-data">
<div className="form-group">
<label for="files" className="btn btn-primary">{uploadButtonData}
<input id="files" type="file" name="csv" className="form-control" hidden accept="*.csv" onChange={handleCSVChange} /></label>
</div>
<button className="btn btn-primary" onClick={uploadCSVData} style={{ float: "right", width: "7rem" }} type="button">
{loading && <i className="fa fa-refresh fa-spin"></i>}
Upload CSV</button>
<div className="mt-3" style={{ textAlign: "center" }}>
<span id="msg" style={{ color: "red" }}>{message}</span>
</div>
</form>
</div>
</div>
</div>
)
Though I'm able to console.log(req.body) and console.log(e.target.files[0]) and getting the acctid and filename but returned empty for the console.log(formData) and console.log(req.file.filename) returned undefined. What have I missed? Many thanks in advance and greatly appreciate any helps. Thanks again
I have managed to solves this by appending the acctid to formData:
const formData = new FormData();
formData.append("csv", csvData);
formData.append("accctid", lsData.acctid);
const response = await Axios.post(process.env.REACT_APP_FETCH_URL + '/api/contact/importcontact', formData, { withCredentials: true })

req.file is showing undefined in console

I am trying to upload a video on youtube from my web app but I am getting an error in sending a file to my backend it is showing the req.file as undefined in req.body it is showing videoFile: "Undefined" and all other values I can see but I am not able to get the file from my react app to backend .
Input.jsx
import axios from "axios";
import React, { useState } from "react";
// import Axios from "axios";
function Input() {
const [form, setForm] = useState({
title: "",
description: "",
file: null,
});
function handleChange(event) {
const InputValue =
event.target.name === "file" ? event.target.file : event.target.value;
setForm({
...form,
[event.target.name]: InputValue,
});
}
function handleSubmit(event) {
event.preventDefault();
console.log({ form });
const headers = {
"content-type": "multipart/form-data",
};
const videoData = new FormData();
videoData.append("videoFile", form.file);
videoData.append("title", form.title);
videoData.append("description", form.description);
axios
.post("http://localhost:3001/upload", videoData, { headers })
.then((res) => {
console.log(res.data);
})
.catch((err) => console.log(err));
}
return (
<div className="form">
<h1>
<i className="fab fa-youtube"></i> Upload video
</h1>
<form onSubmit={handleSubmit} method="POST" enctype="multipart/form-data">
<div>
<input
className="title"
type="text"
placeholder="Title"
name="title"
autoComplete="off"
onChange={handleChange}
/>
</div>
<div>
<textarea
typeof="text"
placeholder="description"
name="description"
autoComplete="off"
onChange={handleChange}
/>
</div>
<div>
<input
type="file"
accept="video/mp4,video/x-m4v,video/*"
placeholder="Add"
name="file"
onChange={handleChange}
/>
</div>
<button className="btn btn-light" type="submit">
Upload Video
</button>
</form>
</div>
);
}
export default Input;
it is node.js part
App.js (backend)
const express = require("express");
const multer = require("multer");
const youtube = require("youtube-api");
const open = require("open");
const cors = require("cors");
const fs = require("fs");
const credentials = require("./client_secret_1013548512515-lti2tpl88m8qqkum1hh095cnevtdi3lu.apps.googleusercontent.com.json");
const app = express();
const bodyParser = require("body-parser");
app.use(express.json());
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
const storage = multer.diskStorage({
destination: "./",
filename: (req, file, cb) => {
const newFileName = `${Date.now()}-${file.originalname}`;
cb(null, newFileName);
},
});
const uploadVideoFile = multer({
storage: storage,
}).single("videoFile");
app.post("/upload", uploadVideoFile, (req, res) => {
console.log(req.body);
console.log(req.file); //it is showing undefined
if (req.file) { //getting error in this part
const filename = req.file.filename;
const { title, description } = req.body;
open(
oAuth.generateAuthUrl({
access_type: "offline",
scope: "https://www.googleapis.com/auth/youtube.upload",
state: JSON.stringify({
filename,
title,
description,
}),
})
);
}
});
app.get("/oauth2callback", (req, res) => {
res.redirect("http://localhost:3000/success");
const { filename, title, description } = JSON.parse(req.query.state);
oAuth.getToken(req.query.code, (err, tokens) => {
if (err) {
console.log(err);
return;
}
oAuth.setCredentials(tokens);
youtube.videos.insert(
{
resource: {
snippet: { title, description },
status: { privacyStatus: "private" },
},
part: "snippet,status",
media: {
body: fs.createReadStream(filename),
},
},
(err, data) => {
console.log("Done");
process.exit();
}
);
});
});
const oAuth = youtube.authenticate({
type: "oauth",
client_id: credentials.web.client_id,
client_secret: credentials.web.client_secret,
redirect_url: credentials.web.redirect_uris[0],
});
PORT = 3001;
app.listen(PORT, () => {
console.log("app is listening on port 3001");
});
Please make the following changes in the Input.jsx file.
// 1. Inside the handleChange() function
const InputValue = event.target.name === "file" ? event.target.files : event.target.value;
// 2. Inside the handleSubmit() function
videoData.append("videoFile", form.file[0], form.file[0].name);
This should output the file object inside req.file.

Resources