Upload article with file in Mern-Stack not works - javascript

I want to upload an article with file. When I set into my request:
headers: {
"Content-Type": "multipart/form-data",
"token": `Bearer ${token}`
},
then I get this Error:
Multipart: Boundary not found.
I have read, that I should not set the Content-Type in my axios-request, but when I do this, I get the 403 error out of my backend. I tried to append all fields in FormData, but I am getting the above error either. I have googled "Boundaries" in axios and tried it with "getHeaders()", but then the console says "getHeaders()" is not a function.
That is my frontend code:
const [formdata, setFormdata] = useState(
{
ressort:"",
theme:"",
title:"",
content:"",
}
)
const {ressort,theme,title,content} = formdata;
const [fileData, setFileData] = useState({
img:""
})
const {img} = fileData;
const fileInput = useRef(img);
const fileChange = (e)=>{
console.log(fileInput.current.files);
setFileData(fileInput.current.files[0])
}
console.log(fileData); //works
const handleChange = (e)=>{
setFormdata((prevState)=>({
...prevState,
[e.target.name]: e.target.value
}))
}
const onSubmit = (e)=>{
e.preventDefault();
const data = new FormData();
data.append("img", fileData);
data.append("ressort", formdata.ressort);
data.append("theme", formdata.theme);
data.append("title", formdata.title);
data.append("content", formdata.content)
console.log(data);
const mainnewsData ={
data,
}
console.log(mainnewsData);
dispatch(createMainNews(mainnewsData));
}
Here is my redux service:
const API_URL = "http://localhost:5000/api/mainNews/";
const createMainNews = async (mainnewsData, token)=>{
const config = {
headers: {
token: `Bearer ${token}`,
'Content-Type': `multipart/form-data;`,
},
// transformRequest: (data, error) => { //gives 403
// return mainnewsData;
// }
}
const response = await axios.post(API_URL, mainnewsData, config);
return response.data;
}
Here is my backend:
router.post("/", upload.single("img"), verifyTokenAndAuthorization, async (req,res)=>{
const newMainNews = new MainNews({
img: req.file.originalname,
ressort: req.body.ressort,
theme: req.body.theme,
title: req.body.title,
content:req.body.content,
});
console.log(newMainNews);
try{
const savedMainNews = await newMainNews.save();
res.status(200).json(savedMainNews);
} catch(error){
res.status(403)
throw new Error("Action failed");
}
});
That is my multer-storage:
const multer = require("multer");
const storage = multer.diskStorage({
destination:(req,file, callback)=>{
callback(null, '../../frontside/public/uploads/')
},
fileName: (req, file, callback)=>{
callback(null, Date.now()+ "--"+ file.originalname)
}
})
const upload = multer({storage:storage});
module.exports = upload;

I believe you are doing everything fine. But just to make this work out of equation. I would suggest you to change your axios header configuration. I hope it will work
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},

I found out the solution by myself. Here is some code you can work with.
The Frontend:
const MainNews = () => {
const { mainnews, isLoading, isError, message} = useSelector((state)=>state.mainnews);
const dispatch = useDispatch();
useEffect(()=>{
if(isError){
window.alert(message);
}
dispatch(getAllMainNews());
return ()=>{
dispatch(reset());
}
}, [dispatch, isError, message]);
//text
const [formdata, setFormdata] = useState(
{
ressort:"",
theme:"",
title:"",
content:"",
}
)
const {ressort,theme,title,content} = formdata;
//file
const [fileData, setFileData] = useState({
img:""
})
const {img} = fileData;
const fileInput = useRef(img);
//change and preview
const [preview, setPreview] = useState("");
const fileChange = (e)=>{
const file = fileInput.current.files[0];
setFileData(file);
handlePreview(file)
}
const handlePreview = (file)=>{
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = ()=>{
setPreview(reader.result);
}
}
console.log(fileData); //works
//text
const handleChange = (e)=>{
setFormdata((prevState)=>({
...prevState,
[e.target.name]: e.target.value
}))
}
const onSubmit = (e)=>{
e.preventDefault();
const mainnewsData = new FormData();
mainnewsData.append("ressort", formdata.ressort);
mainnewsData.append("theme", formdata.theme);
mainnewsData.append("title", formdata.title);
mainnewsData.append("content", formdata.content);
mainnewsData.append("img", fileData);
for(let value of mainnewsData){
console.log(value);
}
dispatch(createMainNews(mainnewsData));
}
http-request in my redux-service:
const createMainNews = async (mainnewsData, token)=>{
const config = {
headers: {
// 'Content-Type': `multipart/form-data`,
'Content-Type' : 'application/json',
token: `Bearer ${token}`,
},
}
const response = await axios.post(API_URL, mainnewsData, config);
console.log(response);
return response.data;
}
multer:
const storage = multer.diskStorage({
destination:(req,file, callback)=>{
callback(null, path.resolve(process.cwd(), 'frontside/public/uploads'));
},
fileName: (req, file, callback)=>{
callback(null, Date.now()+ "--"+ path.extname(file.originalname));
}
})
const upload = multer({storage:storage});
module.exports = upload;
Don't forget in your index.js:
app.use(express.static(path.resolve(process.cwd(), 'frontside/public/')));
And the backend mongoose Schema:
const MainnewsSchema = new mongoose.Schema({
img:{type:String, required:true},
cloudinary_id:{type:String, required:true},
ressort:{type:String, required:true},
theme:{type:String, required:true},
title:{type:String, required:true},
content:{type:String, required:true},
clicked:{type:Number, default:0},
},
My backend:
router.post("/", upload.single("img"), verifyTokenAndAuthorization, async (req,res)=>{
// const {img, cloudinary_id, ressort, theme, title, content} = req.body;
try{
const uploadResult = await cloudinary.uploader.upload(req.file.path,{
upload_preset: "Mern_redux-practice",
resource_type: "auto",
});
// console.log("Upload successfull", JSON.stringify(uploadResult, null, 2));
const newMainNews = new MainNews({
cloudinary_id : uploadResult.public_id,
ressort: req.body.ressort,
theme: req.body.theme,
title: req.body.title,
content: req.body.content,
img: uploadResult.secure_url,
});
console.log(newMainNews);
const savedMainNews = await newMainNews.save();
res.status(200).json(savedMainNews);
} catch(error){
res.status(403)
throw new Error("Action failed");
}
});

Related

Image not being sent in payload using Multer in Nodejs and ReactJs

I am sending an image from my reactjs frontend to my nodejs express backend using formData.
But when i am appending the image it doesn't appear in the payload and i get this error from the backend. TypeError: Cannot read properties of undefined (reading 'filename')
I'm sending the data seemingly correct, so i dont understand why the image is not being sent to the backend in the payload.
this is the payload i am getting.
Here is my code:
Backend
user.js:
const MIME_TYPES = {
'image/png': 'png',
'image/jpeg': 'jpg',
'image/jpg': 'jpg',
}
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "images");
},
filename: (req, file, cb) => {
const name = file.originalname.toLowerCase().split(" ").join("-");
const extension = MIME_TYPES[file.mimetype];
cb(null, name + "-" + Date.now() + "." + extension);
}
})
router.post("/register", multer({ storage: storage }).single("uniIDImage"), (req, res, next) => {
const url = req.protocol + "://" + req.get("host");
const fullUrl = url + "/images/" + req.file.filename;
let User = new models.User();
User.register(req.body, fullUrl).then((response) => {
console.log(response);
if(response) {
return res.json({
msg: 'User registered successfully.',
statusCode:200,
result: response,
});
}
});
});
Reactjs frontend:
registerActions.js:
export const register = (formData) => async dispatch => {
const config = {
headers: {
'Content-Type': 'multipart/form-data;',
}
}
let jsonObject = {};
for (let key of formData.keys()) {
jsonObject[key] = formData.get(key);
console.log( jsonObject[key])
}
try {
const res = await axios.post('/register', jsonObject, config);
dispatch({
type: REGISTER_SUCCESS,
payload: res.data
});
} catch (err) {
dispatch({
type:REGISTER_FAIL,
});
}
}
Register.js
import React, { useEffect, useReducer, useState } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { register } from '../../store/auth/register/actions'
const Register = ({ register }) => {
const [uniIDImage, setUniIDImage] = useState([]);
const [data, setData] = useState({
name: '',
uniID: '',
username: '',
email: '',
phoneNumber: '',
password: '',
});
const handleChange = (e) => {
setData({ ...data, [e.target.name]: e.target.value });
}
const handleImage = (e) => {
setUniIDImage({ pic: e.target.files[0] });
}
const signUp = (e) => {
e.preventDefault();
const formData = new FormData()
formData.append("name", data.name);
formData.append("uniID", data.uniID);
formData.append("username", data.username);
formData.append("email", data.email);
formData.append("phoneNumber", data.phoneNumber);
formData.append("uniIDImage", uniIDImage.pic);
formData.append("password", data.password);
console.log(uniIDImage.pic);
register(formData);
}
return (
<div className="mb-3">
<Label className="form-label">ID Photo</Label>
<Input
id="fileInput"
name="uniIDImage"
className="form-control"
accept="image/*"
type="file"
onChange={handleImage}
/>
)
Register.propTypes = {
register: PropTypes.func.isRequired,
}
export default connect(null, { register })(Register);
When you convert form data to JSON object, you lose the file, and there is no point converting it, and also no need to set content-type header.
So, simply pass formData to axios as a second argument:
export const register = (formData) => async dispatch => {
try {
const res = await axios.post('/register', formData);
dispatch({
type: REGISTER_SUCCESS,
payload: res.data
});
} catch (err) {
dispatch({
type: REGISTER_FAIL,
});
}
}

MERN - Getting error: "Cannot read property 'path' of undefined" when trying to upload a pdf file using multer

Like I said on the title, I'm facing that error when I'm trying to upload a file from the client-side. Here are the code blocks for more context:
Route:
const express = require("express");
const { upload } = require("../middlewares/uploadMiddleware");
const { registerResearchSheet } = require("../controllers/researchSheetControllers");
const router = express.Router();
router.route("/").post(upload.single("file"), registerResearchSheet);
Middleware:
const multer = require("multer");
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "backend/uploads/");
},
filename: function (req, file, cb) {
cb(
null,
new Date().toISOString().replace(/:/g, "-") + "-" + file.originalname
);
},
});
const fileFilter = (req, file, cb) => {
if (file.mimetype === "application/pdf") {
cb(null, true);
} else {
cb(null, false);
}
};
const upload = multer({ storage: storage, fileFilter: fileFilter });
module.exports = { upload };
Controller:
const asyncHandler = require("express-async-handler");
const ResearchSheet = require("../models/researchSheetModel");
const registerResearchSheet = asyncHandler(async (req, res) => {
const {
title,
director,
coordinator,
students,
reviewers,
proposalType,
investigationType,
status,
} = req.body;
if (!req.file) {
res.status(400);
throw new Error("The file hasn't be uploaded correctly"); **//This is the triggering error**
}
const file = req.file.path;
const researchSheetExists = await ResearchSheet.findOne({ title, students });
if (researchSheetExists) {
res.status(400);
throw new Error("Title or students can't be repeated");
}
const researchSheet = await ResearchSheet.create({
title,
file, // file: buffer,
director,
coordinator,
students,
reviewers,
proposalType,
investigationType,
status,
});
if (researchSheet) {
res.status(201).json({
_id: researchSheet._id,
title: researchSheet.title,
file: researchSheet.file,
director: researchSheet.director,
coordinator: researchSheet.coordinator,
students: researchSheet.students,
reviewers: researchSheet.reviewers,
proposalType: researchSheet.proposalType,
investigationType: researchSheet.investigationType,
status: researchSheet.status,
});
} else {
res.status(400);
throw new Error("An error has ocurred");
}
});
And finally the frontend part:
const [file, setFile] = useState(null);
//code..
const singleFileUpload = (e) => {
setFile(e.target.files[0]);
};
const submitHandler = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("title", title);
formData.append("file", file);
formData.append("director", director);
formData.append("coordinator", coordinator);
formData.append("students", students);
formData.append("proposalType", proposalType);
formData.append("investigationType", investigationType);
console.log(file); //This log is correctly showing the File and its properties
if (
!title ||
!coordinator ||
!students ||
!proposalType ||
!investigationType
) {
setMessage("You must fill all the fields");
} else {
dispatch(registerResearchSheetAction(formData));
// history.push("/listresearchsheets");
}
};
//more code...
<Form.Control
className="mb-3"
type="file"
accept=".pdf"
onChange={(e) => singleFileUpload(e)}
/>
<Button type="submit">Register</Button>
Action:
export const registerResearchSheetAction =
(
title,
file,
director,
coordinator,
students,
reviewers,
proposalType,
investigationType,
status
) =>
async (dispatch) => {
try {
dispatch({
type: RESEARCHSHEET_REGISTER_REQUEST,
});
const formData = new FormData();
formData.append("file", file);
formData.append("title", title);
formData.append("director", director);
formData.append("coordinator", coordinator);
formData.append("students", students);
formData.append("reviewers", reviewers);
formData.append("proposalType", proposalType);
formData.append("investigationType", investigationType);
formData.append("status", status);
const config = {
headers: {
"Content-Type": "multipart/form-data",
},
};
const { data } = await axios.post(
"/api/researchsheets",
formData,
config
);
dispatch({ type: RESEARCHSHEET_REGISTER_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: RESEARCHSHEET_REGISTER_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
I already tested the functionality on the server-side using Postman and it is uploading, more than the file, the route/path of it successfully on the database. That's why I'm thinking that the problem must be on the client-side. I really don't know what I'm missing here, so that's why I came for a little of help, I hope I was clear on my explanaiton.
Thank you in advance!

My Fetched Data isn't Storing in the State

As the title suggests my fetched data isn't storing in the state. I think it is something to do with the loading sequence but I can't be sure because I am still new, especially when it comes to axios fetching. Using the useEffect to check the state I see that formData.organizer starts empty, get the userId string, then gets emptied again.
usersController
exports.myAccount = async (req, res) => {
try {
const user = await User.find({ email: req.email }, { _id: 1 });
res.status(201).json({
status: 'success',
user,
});
} catch (err) {
res.status(404).json({
status: 'fail',
message: err,
});
}
};
Create Event Container
const [formData, setFormData] = useState({
...
organizer: '',
})
useEffect(() => {
getMyAccount();
}, []);
const getMyAccount = async () => {
let data;
try {
const res = await axiosPrivate.get('/api/users/myaccount');
data = await res.data.user[0]._id;
setFormData({
...formData,
organizer: data,
});
} catch (err) {
console.log(err);
}
};
useEffect(() => {
console.log(`organizer in formData:${formData.organizer}`);
}, [formData.organizer]);
import axios from 'axios';
const BASE_URL = 'http://localhost:5000';
export default axios.create({
baseURL: BASE_URL,
});
export const axiosPrivate = axios.create({
baseURL: BASE_URL,
headers: {
'Content-Type': 'application/json',
},
withCredentials: true,
});
Thanks in advance for any help!

Uploading a Blob held in State to AWS S3

I have implemented a mic-recorder-to-mp3 component in a ReactJS NextJS app which stores a voice-memo recorded in the browser by the user and saves the resulting blob to React state, as well as a resulting MP3 to state as well.
I am struggling to upload either the blob or the MP3 file to AWS S3 - the problem is evident in that I cannot parse the req.body string which is received by the API.
Here is some code! This is the function that stores the raw audio as state:
const [audioBlob, setAudioBlob] = useState(null)
const [blobURL, setBlobUrl] = useState(null)
const [audioFile, setAudioFile] = useState(null)
const stopRecording = () => {
recorder.current
.stop()
.getMp3()
.then(([buffer, blob]) => {
const file = new File(buffer, 'audio.mp3', {
type: blob.type,
lastModified: Date.now()
})
setAudioBlob(blob)
const newBlobUrl = URL.createObjectURL(blob)
setBlobUrl(newBlobUrl)
setIsRecording(false)
setAudioFile(file)
})
.catch(e => console.log(e))
}
And this is the function that sends the payload to the API:
const submitVoiceMemo = async () => {
const filename = encodeURIComponent(audioFile)
const res = await fetch(`/api/upload-url?file=${filename}`)
const { url, fields } = await res.json()
const formData = new FormData()
Object.entries({ ...fields, audioFile }).forEach(([key, value]) => {
formData.append(key, value)
})
const upload = await fetch(url, {
method: 'PUT',
body: formData
})
if (upload.ok) {
console.log('Uploaded successfully!')
} else {
console.error('Upload failed.')
console.error()
}
}
This is the upload-url API Route:
module.exports = async (req, res) => {
try {
aws.config.update({
accessKeyId: process.env.AWS_ACCESS_KEY_1,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY_ID,
region: 'us-east-2',
signatureVersion: 'v4'
})
const s3 = new aws.S3()
const post = await s3.createPresignedPost({
Bucket: 'waveforms',
Key: `voicememo/${req.query.file}`,
ContentType: 'audio/mpeg',
ACL: 'public-read',
Expires: 60
})
res.status(200).json(post
} catch (e) {
res.status(500).json({ error: e.message })
}
}
It currently returns a 400 Bad Request error.
This is an alternative solution, which does upload an MP3 successfully to S3, however the file is not playing when accessed via the S3 console, although it does have a filesize.
module.exports = requireAuth(async (req, res) => {
try {
AWS.config.update({
accessKeyId: process.env.AWS_ACCESS_KEY_1,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY_ID,
region: 'us-east-2',
signatureVersion: 'v4'
})
const s3 = new AWS.S3();
const uuid = randomUUID()
const s3Params = {
Bucket: 'waveforms',
Key: `voicememo/${uuid}.mp3`,
Body: req.body,
ContentType: 'audio/mp3',
ACL: 'public-read'
}
await s3.upload(s3Params).promise()
} catch (e) {
res.status(500).json({ error: e.message })
}
})
And this is the accompanying fetch request.
const submitVoiceMemo = async () => {
try {
await fetch('/api/createVoiceMemo', {
method: 'PUT',
body: audioBlob
})
} catch (error) {
console.error(error)
}
}

S3 Upload Failing Silently in Production

I'm struggling to debug a NextJS API that is working in development (via localhost) but is silently failing in production.
Below, the two console.log statements are not returning, so I suspect that the textToSpeech call is not executing correctly, potentially in time?
I'm not sure how to rectify, happy to debug as directed to resolve this!
const faunadb = require('faunadb')
const secret = process.env.FAUNADB_SECRET_KEY
const q = faunadb.query
const client = new faunadb.Client({ secret })
const TextToSpeechV1 = require('ibm-watson/text-to-speech/v1')
const { IamAuthenticator } = require('ibm-watson/auth')
const AWS = require('aws-sdk')
const { randomUUID } = require('crypto')
import { requireAuth } from '#clerk/nextjs/api'
module.exports = requireAuth(async (req, res) => {
try {
const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
})
const textToSpeech = new TextToSpeechV1({
authenticator: new IamAuthenticator({
apikey: process.env.IBM_API_KEY
}),
serviceUrl: process.env.IBM_SERVICE_URL
})
const uuid = randomUUID()
const { echoTitle, chapterTitle, chapterText } = req.body
const synthesizeParams = {
text: chapterText,
accept: 'audio/mp3',
voice: 'en-US_KevinV3Voice'
}
textToSpeech
.synthesize(synthesizeParams)
.then(buffer => {
const s3Params = {
Bucket: 'waveforms/audioform',
Key: `${uuid}.mp3`,
Body: buffer.result,
ContentType: 'audio/mp3',
ACL: 'public-read'
}
console.log(buffer.result)
console.log(s3Params)
s3.upload(s3Params, function (s3Err, data) {
if (s3Err) throw s3Err
console.log(`File uploaded successfully at ${data.Location}`)
})
})
.catch(err => {
console.log('error:', err)
})
const dbs = await client.query(
q.Create(q.Collection('audioform'), {
data: {
title: echoTitle,
published: 2022,
leadAuthor: 'winter',
user: req.session.userId,
authors: 1,
playTime: 83,
chapters: 1,
gpt3Description: '',
likes: 20,
image:
'https://waveforms.s3.us-east-2.amazonaws.com/images/Mars.jpeg',
trackURL: `https://waveforms.s3.us-east-2.amazonaws.com/audioform/${uuid}.mp3`,
albumTracks: [
{
title: chapterTitle,
text: chapterText,
trackURL: `https://waveforms.s3.us-east-2.amazonaws.com/audioform/${uuid}.mp3`
}
]
}
})
)
res.status(200).json(dbs.data)
} catch (e) {
res.status(500).json({ error: e.message })
}
})
Replace the async fragments something like this, assuming they are meant to be executed sequentially.
try {
// code removed here for clarity
const buffer = await textToSpeech.synthesize(synthesizeParams);
const s3Params = {
Bucket: 'waveforms/audioform',
Key: `${uuid}.mp3`,
Body: buffer.result,
ContentType: 'audio/mp3',
ACL: 'public-read'
}
await s3.upload(s3Params).promise();
const dbs = await client.query(...);
res.status(200).json(dbs.data);
} catch (e) {
res.status(500).json({ error: e.message });
}

Categories

Resources