I've had trouble saving an uploaded file sent through a VueJS frontend to a Node + Express backend. Data is sent from the frontend through multipart form data and as such all the metadata and the file itself is appended within the request body. The following is the router on the backend
import {submitFlag} from '../some/dir/controller.js'
const router = express.Router();
const app = express();
app.use(express.json());
router.post('/submit-flag', [], submitFlag)
and the backend controller that handles the API request from the router
import multer from 'multer';
const evidencePathSave = '../uploads/flagEvidence'
export const submitFlag = async (req, res) => {
const evidenceStorage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, evidencePathSave)
},
filename: function (req, file, cb) {
cb(null, req.body.flagEvidence.name);
}
})
const uploadEvidence = multer({
storage: evidenceStorage,
fileFilter: (req, res, cb) => {
const filetypes = /pdf|doc|docx|txt/;
const mimetype = filetypes.test(req.body.flagEvidence.type);
const extname = filetypes.test(path.extname(req.body.flagEvidence.name));
// if mimetype && extname are true, then no error
if(mimetype && extname){
return cb(null, true);
}
// if mimetype or extname false, give an error of compatibilty
return cb("The uploaded file, isn't compatible");
}
}).fields([
{name: 'flagEvidence', maxCount: 1}
]);
uploadEvidence(req, res, function(err) {
if (err) {
console.log(err)
res.status(500).send("Error in Saving");
}
})
res.status(200)
}
The function does not return any error nor status 500 message however when checking the local storage, the file does not seem to be found anywhere. Removing the file filter function does not seem to work.
The frontend Vue function is as follow
submitFlag(event) {
this.flagEvidence = event.target.files[0]
var formData = new FormData()
formData.append('flagSubject', this.flagSubject)
formData.append('flagWriteup', this.flagWriteup)
formData.append('flagEvidence', this.flagEvidence)
formData.append('userID', this.user._id)
formData.append('username', this.user.username)
formData.append('publicationID', this.publicationID)
formData.append('filename', this.flagEvidence.name)
axios({
method: "POST",
url: serverSide.submitFlag,
data: formData,
headers: {"Content-type": "multipart/form-data"}
})
.then((res) => {
this.$router.push({name: "news", query:{id: this.publicationID}})
})
.catch(function (error) {
console.error(error.response);
});
}
Your usage of multer is incorrect. multer.fields()
returns a function that should be passed as middleware to your route. Here is how that would look like in your case:
const evidenceStorage = multer.diskStorage({
destination: function (req, file, cb) { ... },
filename: function (req, file, cb) {
cb(null, file.originalname);
},
});
const uploadEvidence = multer({
storage: evidenceStorage,
fileFilter: (req, res, cb) => { ... },
);
router.post(
'/submit-flag',
uploadEvidence.fields([{ name: 'flagEvidence', maxCount: 1 }]),
submitFlag
);
Also instead of req.body.flagEvidence.name
I'd use file.originalname
when configuring DiskStorage
. Have a look at this free Request Parsing in Node.js Guide when working with file uploads in Node.js.