I have a nextjs app with a form in which I give the user the option to upload a file at the same time as filling out their personal data. I am using Multer and in the next-connect middleware I am using, it has me set bodyParser to false. As I am collecting other data, I get an error 501 cannot implement saying "error": "Sorry something Happened! Cannot destructure property 'firstName' of 'req.body' as it is undefined."
I then resorted to another piece of middleware called multiparty which I am using in the addcv route of the nextjs api. I am still getting the same error. If anyone can help me sort out this issue, I would appreciate it. I don't understand why it is not possible to upload a file at the same time as the form data as I could do this in a normal nodejs app without issue.
Here are the relevant parts of the Form:
CVForm.jsx
import { useSubmitCV } from '../hooks/useSubmitCV';
function CVForm() {
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
email: '',
phone: '',
cvfile: '',
address: '',
usEligible: '',
city: '',
state: '',
zip: '',
dob: '',
description: '',
});
const [blockOneOpen, setBlockOneOpen] = useState(false);
const [havecv, setHaveCv] = useState(false);
const [haveNoCv, setHaveNoCv] = useState(false);
const [buttonClicked, setButtonClicked] = useState(false);
const [buttonText, setButtonText] = useState('Click Here To Start');
const { submitcv, error, loading } = useSubmitCV();
const onChange = (e) => {
setFormData((prevState) => ({
...prevState,
[e.target.name]: e.target.value,
}));
};
const onFileChange = (e) => {
if (!cvfile) {
setFormData((prevState) => ({
...prevState,
cvfile: null,
}));
} else {
setFormData((prevState) => ({
...prevState,
cvfile: e.target.files[0],
}));
}
};
const cvSubmit = async (e) => {
e.preventDefault();
if (
formData.firstName === '' ||
formData.lastName === '' ||
formData.email === '' ||
formData.phone === ''
) {
toast.error('All Fields are required');
} else {
await submitcv(formData);
}
console.log(formData);
setButtonClicked(!buttonClicked);
setButtonText('Start Here');
};
// JSX PORTION
<label className='block mb-6 mt-6 text-sm font-medium text-gray-900 dark:text-gray-300'
htmlFor='file_input'>Upload Resume
</label>
<input
className='block w-full text-sm text-white bg-gray-50 rounded-lg border border-gray-300 cursor-pointer dark:text-white focus:outline-none dark:bg-[#00388d] dark:border-gray-600 dark:placeholder-white'
id='cvfile'
type='file'
filename='cvfile'
name='cvfile'
onChange={onFileChange}
accept='.doc,.docx,.pdf,.odf' />
<div className='mb-6 mt-6'>
<button type='submit'className='w-full px-2 py-4 text-white bg-[#00388d] rounded-md focus:bg-indigo-600 focus:outline-none'>
Send Resume
</button>
</div>
The Use Submit Hook useSubmitCV.js
import { useState } from 'react';
import { toast } from 'react-toastify';
import axios from 'axios';
import { useRouter } from 'next/router';
export const useSubmitCV = () => {
const [error, setError] = useState(null);
const [loading, setLoading] = useState(null);
const router = useRouter();
// const { dispatch } = useCVContext();
const API_URL = '/api/cvs/addcv';
const submitcv = async (formData) => {
setLoading(true);
setError(null);
const form = new FormData();
form.append('firstName', formData.firstName);
form.append('lastName', formData.lastName);
form.append('email', formData.email);
form.append('phone', formData.phone);
form.append('cvfile', formData.cvfile);
form.append('fileUrl', formData.fileUrl);
form.append('address', formData.address);
form.append('city', formData.city);
form.append('state', formData.state);
form.append('zip', formData.zip);
form.append('usEligible', formData.usEligible);
form.append('dob', formData.dob);
form.append('description', formData.description);
const response = await axios.post(API_URL, form);
if (response.data.message) {
setError(response.data.message);
toast.error(response.data.message);
setLoading(false);
return;
}
toast.success('Resume Successfully Submitted');
setLoading(false);
router.push('/');
};
return { submitcv, loading, error };
};
The Middleware file middleware.js
import nextConnect from 'next-connect';
import multiparty from 'multiparty';
const middleware = nextConnect();
middleware.use(async (req, res, next) => {
const form = new multiparty.Form();
await form.parse(req, function (err, fields, files) {
req.body = fields;
req.files = files;
next();
});
});
export default middleware;
And Finally the addcv.js Route within the api route of nextjs
import nextConnect from 'next-connect';
import multer from 'multer';
import { v4 as uuidv4 } from 'uuid';
import fs from 'fs';
import path from 'path';
import middleware from '../../../middleware/middleware';
import { File, Web3Storage } from 'web3.storage';
import CV from '../../../models/cvModel';
const w3storage = new Web3Storage({ token: process.env.WEB3_STORAGE_TOKEN });
function checkFileType(file, cb) {
// Allowed ext
const filetypes = /doc|docx|pdf|odf/;
// Check ext
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
// Check mime
const mimetype = filetypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
} else {
cb('Error: Documents Only!');
}
}
const storage = multer.memoryStorage({
destination: (req, file, cb) => {
cb(null, path.join(process.cwd(), 'public', 'uploads'));
},
filename: (req, file, cb) => {
cb(null, uuidv4() + '-' + file.originalname);
},
fileFilter: function (_req, file, cb) {
checkFileType(file, cb);
},
});
const maxSize = 3 * 1000 * 1000;
const upload = multer({ storage: storage, limits: { fileSize: maxSize } });
const createCV = nextConnect({
onError(error, req, res) {
res
.status(501)
.json({ error: `Sorry something Happened! ${error.message}` });
},
onNoMatch(req, res) {
res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
},
})
.use(middleware)
.use(upload.single('cvfile'))
.post((req, res) => {
// @desc Create a new cv
// @route POST /api/cvs/addcv
// @access Public
const {
firstName,
lastName,
email,
phone,
cvfile,
address,
city,
state,
zip,
usEligible,
dob,
description,
} = req.body;
const fileUrl = req.file.filename;
console.log(fileUrl);
const bytes = fs.readFileSync(`${process.env.UPLOAD_PATH}/${fileUrl}`);
const newFile = new File([bytes], fileUrl);
const cid = w3storage.put([newFile]);
const filePath = path.join(process.cwd(), 'public', 'uploads', fileName);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
const cv = CV.create({
firstName,
lastName,
email,
phone,
cvfile,
address,
city,
state,
zip,
usEligible,
dob,
description,
fileUrl: `https://${cid}.ipfs.dweb.link/${fileName}`,
});
res.status(201).json(cv);
console.log(req.body);
// res.status(201).json({ body: req.body, file: req.file });
});
export default createCV;
export const config = {
api: {
bodyParser: false, // Disallow body parsing, consume as stream
},
};
Again, if anyone knows how to do this without having to go outside of nextjs api routing system, I would sure appreciate it. Thank you in advance.
EDIT::
I ran a debugger and got the following from the form values after submit in the useSubmit.js

This shows the value of cvfile as cv.docx which is correct but for the value of fileUrl (which is kind of important :) ) is set to undefined. Not good. And worse yet, because I am new to Next and a little green in this area, I don't know how to fix it.
EDIT 2:: On Debugger; my code dies after this block of code:
const response = await axios.post(API_URL, form, {
headers: {
'Content-Type': 'multipart/formdata',
},
});