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

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,
});
}
}

Related

Send file attachments in next.js via nodemailer

I am trying to send file attachments in my Next.js app using nodemailer.
My current setup consists of:
ApplicationForm.tsx
import { sendEmailForm } from '../../../../lib/api';
const initialState: IApplicationFormData = {
files: [],
};
const ApplicationForm = () => {
const [formData, setFormData] = useState<IApplicationFormData>(initialState);
const handleFileUpload = (
e: React.ChangeEvent<HTMLInputElement> | React.DragEvent<HTMLDivElement>
) => {
e.preventDefault();
let fileList;
if (e.type === 'change') {
fileList = (e.target as HTMLInputElement).files;
} else {
fileList = (e as React.DragEvent<HTMLDivElement>).dataTransfer.files;
}
const fileArray = Array.from(fileList || []);
setFormData((current) => ({
...current,
files: [...current.files, ...fileArray],
}));
};
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
try {
await sendEmailForm(formData, 'applicationMail');
setFormData(initialState);
} catch (error: any) {
console.log(error.message)
});
}
};
return(
<FileInput
name={fileLabel}
labelText={fileLabel}
onChange={handleFileUpload}
filePreview={formData.files}
removeFile={removeFile}
removeAllFiles={removeAllFiles}
handleDragOver={handleDragOver}
/>
)
export default ApplicationForm;
I save the uploaded files in the state as an array.
Following the fetch function in lib/api.ts
export const sendEmailForm = async (
data: any,
apiEndpoint: 'applicationMail' | 'contactMail' | 'callbackMail'
) => {
fetch(`/api/${apiEndpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(data),
}).then((res) => {
if (!res.ok)
throw new Error(`Failed to send email, status code: ${res.status}`);
return res.json();
});
};
Next.js API applicationMail.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import {
applicationEmailTransporter,
applicationMailOptions,
} from '../../services/nodemailer';
import { generateEmailTemplate } from './../../lib/emailTemplate';
const applicationFormHandler = async (
req: NextApiRequest,
res: NextApiResponse
) => {
const data = req.body;
if (req.method === 'POST') {
if (!data.firstName || !data.lastName || !data.email || !data.message)
return res.status(400).json({ message: 'Bad request' });
}
try {
await applicationEmailTransporter.sendMail({
...applicationMailOptions,
...generateEmailTemplate(data),
subject: 'New message',
});
if (data.email && data.email.length > 0) {
try {
await applicationEmailTransporter.sendMail({
to: data.email,
from: process.env.NEXT_PUBLIC_APPLICATION_EMAIL,
subject: 'subject',
text: `text`,
html: `<p>html</p>`,
});
} catch (error) {
console.log(error.message)
}
}
return res.status(200).json({ message: 'Success' });
} catch (error: any) {
return res.status(400).json({ message: error.message });
}
};
export default applicationFormHandler;
Lastly the nodemailer.js for the transporter
import nodemailer from 'nodemailer';
const applicationEmail = process.env.NEXT_PUBLIC_APPLICATION_EMAIL;
const applicationEmailPassword =
process.env.NEXT_PUBLIC_APPLICATION_EMAIL_PASSWORD;
export const applicationEmailTransporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: applicationEmail,
pass: applicationEmailPassword,
},
});
export const applicationMailOptions = {
from: applicationEmail,
to: applicationEmail,
attachments: [
{
// Retrieve formData.files data from ApplicationForm.tsx
// filename: 'test.png',
// path: './public/assets/test.png',
},
],
};
My Question is if it would be possible to send attachments containing the file data from my ApplicationForm.tsx using just nodemailer or would i need a package like multer?
How would i refactor my current setup to properly send the uploaded files.
Thanks in advance!

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!

Multer doesn't upload my Image to my Backend

I try to implement multer to upload files for my galery.
Unfortunately it doesn't work.
Network shows me this error:
msg: "Cannot read properties of undefined (reading 'filename')"
I guess the error is somewhere here:
upload.single('galery')
Bcs i try to console.log my file insite the storage function and it seems like it's not used at all. I couldn't even print a "Hello World" in there atm.
That's why I'm getting an error after:
const image = req.file.filename;
const router = require('express').Router();
const galeryCtrl = require('../controllers/galeryCtrl');
const multer = require('multer');
const shortid = require('shortid');
const path = require('path');
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '../images');
},
filename: function (req, file, cb) {
console.log(file);
cb(
null,
shortid.generate() + '-' + Date.now() + path.extname(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: storage, fileFilter });
router
.route('/galery')
.get(galeryCtrl.getGaleries)
.post(upload.single('galery'), async (req, res) => {
try {
const image = req.file.filename;
const galeryCheck = await Galery.findOne({ image });
// Check if already exist
if (galeryCheck)
return res.status(400).json({ msg: 'already exists' });
const newGalery = new Galery({ image, name: shortid.generate() });
await newGalery.save();
res.json({ msg: 'success' });
} catch (err) {
return res.status(500).json({ msg: err.message });
}
});
const onFileChange = (e) => {
setGalery(e.target.files[0]);
};
const onSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('galery', galery);
console.log(formData);
axios({
url: '/api/galery',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data',
},
data: formData,
})
.then((response) => {
return response;
})
.catch((error) => {
console.log(error);
});
};
<form onSubmit={onSubmit}>
<input type='file' onChange={onFileChange} />
<button type='submit'>Submit</button>
</form>
I tried to change the code but this brings new errors.
First i was need to remove the value={galery} from my input, otherwise I am getting a white screen.
But now I've new errors:
POST http://localhost:3000/api/galery 500 (Internal Server Error)
Error: Request failed with status code 500 (from the console.log(error)
Backend code looks good to me but while sending the file in the request can you try like below.
onFileChange(e) {
setGalery(e.target.files[0])
}
onSubmit(e) {
e.preventDefault()
const formData = new FormData()
formData.append('galery', galery)
axios.post("serverurlhere", formData, {
}).then(res => {
console.log(res)
})
}
// OR ANOTHER WAY
onSubmit(e) {
e.preventDefault()
const formData = new FormData();
formData.append('galery', galery)
axios({
url:"serverurlhere",
method:'post',
headers:{
'Content-Type': 'multipart/form-data'
},
data: formData,
})
.then(response => {
return response;
})
.catch(error =>{
console.log(error);
});
}
<form onSubmit={this.onSubmit}>
<input type="file" onChange={this.onFileChange} />
<button type="submit">Submit</button>
</form>

Upload article with file in Mern-Stack not works

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");
}
});

Nodejs MongoDB - add new entry Post()

so trying to post new entry into MongoDB. but getting this error in Redux Product validation failed: name: Path 'name' is required., description: Path 'description' is required.
nodejs version: 14.9.0
and Atlas mongodb.
frontEnd addProduct.js page:
import { createProduct } from '../redux/actions/productActions'
const [name, setName] = useState('')
const [description, setDescription] = useState('')
const createProductHandler = (e) => {
e.preventDefault()
dispatch(createProduct({
name,
description
}))
}
const nameHandler = (e) => {
setName(e.target.value)
}
const descriptionHandler = (e) => {
setDescription(e.target.value)
}
return (
<input type='text' onChange={nameHandler} />
<input type='text' onChange={descriptionHandler} />
<input type="submit" value='submit' onClick={createProductHandler} />
)
productController:
const createdProduct = asyncHandler(async (req, res) => {
const mongoProduct = async (data) => {
return new Product({
name: data.name,
description: data.description
})
}
const product = await mongoProduct(req.body)
const createdProduct = await product.save()
res.status(201).json(createdProduct)
})
productActions:
export const createProduct = () => async (dispatch, getState) => {
try {
dispatch({
type: 'PRODUCT_CREATE_REQUEST',
})
const {
userLogin: {userInfo},
} = getState()
const config = {
headers: {
Authorization: `Bearer ${userInfo.token}`,
},
}
const { data } = await axios.post(`/api/products`, {}, config)
dispatch({
type: 'PRODUCT_CREATE_SUCCESS',
payload: data
})
} catch (error) {
dispatch({
type: 'PRODUCT_CREATE_FAIL',
payload:
error.response && error.response.data.message
? error.response.data.message
: error.meessage,
})
}
}
productReducers.js:
export const productCreateReducer = (state = {}, action) => {
switch (action.type) {
case 'PRODUCT_CREATE_REQUEST':
return {loading: true}
case 'PRODUCT_CREATE_SUCCESS':
return {loading: false, success: true, product: action.payload}
case 'PRODUCT_CREATE_FAIL':
return {loading: false, error: action.payload}
default:
return state
}
}
alternatively when i try to populate the database from post-man using this code in productController.js it works fine with sample data:
const createdProduct = asyncHandler(async (req, res) => {
const product = new Product({
name: 'Sample Name',
description: 'Sample Description'
})
const createdProduct = await product.save()
res.status(201).json(createdProduct)
})
plus im getting POST ipadress/api/products 500 (Internal Server Error) in console
You can config your axios api service config to separate file and use axios
const request = axios.create({
// baseURL: 'https://mock-api.com',
baseURL: BASE_URL ,
timeout: 5000
})
request.interceptors.request.use(
config => {
// get token
if (// check your token) {
config.headers["Authorization"] = "Bearer ${your-token}"
}
return config
},
error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
// Can also config axios response interceptors to handle API error
Your redux action
import axiosInstance from './your-axios-config-path'
export const createProduct = (product) => async (dispatch, _getState) => {
try {
dispatch({ type: 'PRODUCT_CREATE_REQUEST' })
const response = await axiosInstance.post(`/api/products`, {...product})
dispatch({
type: 'PRODUCT_CREATE_SUCCESS',
payload: response?.data ?? {}
})
} catch (error) {
dispatch({
type: 'PRODUCT_CREATE_FAIL',
payload: // error message,
})
}
}
Alternatively, you can use Redux Toolkit, It much easier to setup store and using. It includes createAsyncThunk, RTK Query to handle side effect.

Categories

Resources