Multer req.file is always undefined despite file object created - reactjs

I read all the questions related to this already and nothing works. I spent the entire day on this and I can't figure out what's wrong.
I am trying to upload a file to my database using React, mongoose, mongoDB and express/node.
Here is what I already did:
Added method="post" and enctype="multipart/form-data" to my form
the "name" property of the file input is "icon" and in the middleware upload.single("icon") too.
On submit I have a function action that is called.
export const updateUserIconette = icon => async dispatch => {
console.log("Number 1");
console.log(icon);
try {
const res = await axios.post("/me", icon);
dispatch({
type: UPDATE_PROFILE_SUCCESS,
payload: res.data
});
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, "danger")));
}
dispatch({
type: UPDATE_PROFILE_ERROR
});
}
};
Here, the console.log number 1 logs and the log of icon too, which is a file object.
And the API Post route is here:
const multer = require("multer");
const storage = multer.diskStorage({
destination: function(req, file, callback) {
callback(null, "./uploads/");
},
filename: function(req, file, callback) {
callback(null, file.originalname);
}
});
const fileFilter = (req, file, callback) => {
if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
callback(null, true);
} else {
callback(null, false);
}
};
const upload = multer({ storage: storage, fileFilter: fileFilter });
router.post(
"/",
[
auth,
upload.single("icon")
],
async (req, res) => {
console.log("number 2")
console.log(req.file)
// Build the Profile object
const profileFields = {};
profileFields.user = req.user.id;
if (req.body.username) profileFields.username = req.body.username;
if (req.body.bio) profileFields.bio = req.body.bio;
if (req.body.location) profileFields.location = req.body.location;
if (req.file) profileFields.icon = req.file.path;
try {
let user = await User.findOne({ _id: req.user.id });
if (user) {
//Update
user = await User.findOneAndUpdate(
{ _id: req.user.id },
{ $set: profileFields },
{ new: true }
);
return res.json(user);
}
await user.save();
res.json(user);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
Here, the console.log number 2 logs but not the req.file because it is empty.

Alright, here's a working demo using react, multer, express, mongodb and mongoose. It's not fancy, pretty much bare bones, but it works. What you should do is step through your project and make sure each file/function/middleware works as intended in the flow (see flow example below).
Github repo: https://github.com/mattcarlotta/upload-image-example
Follow the README for instructions on how to run it.
All important files will have notes as to what their function does.
Files to focus on:
client-side form (specifically sending FormData here)
multer middleware
upload icon route
saveIcon middleware
createIcon controller
Flow:
User adds a file (image) and an email address and clicks Submit to send a POST request to the API.
The POST request first passes through the multer middleware.
If valid, then the request is handled via the upload icon route.
The route passes it to the saveIcon middleware.
The saveIcon middleware saves the image to uploads then passes the request to the createIcon controller.
The controller saves the email and icon filepath to the mongodb database, and then sends back a response (message and remoteicon) to the client-side form.
The client-side form receives a res (response) from the API and sets it to state.
When the state is updated, the remoteicon is set as a src to an img element, which in turn, makes a GET request back to the API, which in turns sends back the uploaded image.
Ideally, most of this flow will be a microservice (and not attached to your main application), as it will vastly simplify your structure. More info can be found here.

Related

invalid JSON error when clicking button in reactjs

I am trying to build a simple react page where there are two buttons at the beginning.
The first sends the string 'private' as response when clicked, the second sends 'public' as a response when clicked.
The two strings should come from a mongodb database. It's not done yet, because whenever I click on any of the buttons, I get the same error over and over again.
It's the following:
Uncaught (in promise) SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
This project has a backend server with two GET requests and a react frontend.
You can see them below.
The frontend:
function PubPrivBtn() {
async function clickHandler(e) {
if (e.target.id === 'pubBtn') {
let response = await fetch('/api/public');
let result = await response.json();
console.log(result);
}
if (e.target.id === 'privBtn') {
let response = await fetch('/api/private');
let result = await response.json();
console.log(result)
}
}
return (
<div>
<button onClick={(e) => clickHandler(e)} id='pubBtn'>Public</button>
<button onClick={(e) => clickHandler(e)} id='privBtn'>Private</button>
</div>
)}export default PubPrivBtn;
And the backend:
import mongoose from "mongoose";
import express from "express";
import props from './porp.model.js'
const app = express();
app.use(express.json());
mongoose.connect('mongodb://localhost/auth', async (err, db) => {
if (err) {
console.log(err)
}
else{
console.log('Connected!')
}
app.get('/api/public', async (req, res) => {
let pub = await props.where("vis").equals("public")
console.log(pub);
res.send(pub)
})
app.get('/api/private', async (req, res) => {
let priv = await props.where("vis").equals("private")
console.log(priv);
res.send(priv)
})
})
app.listen(3001)
Plus the schema is const porp = new mongoose.Schema({ vis: String }).
I have tried to remake the whole project from zero (not like it was a big deal), but nothing changed, so I have to think the problem is not in my code. Or maybe I'm wrong and it's something obvious.
My "proxy": "http://localhost:3001", was in the wrong package.json. (the backend's)
I have replaced it to the frontend and it's now working perfectly fine.
Thanks #Konrad Linkowski! If I hadn't noticed it, your comment would have solved it for sure!

Uploading React - Node image(s) to Cloudinary

Has anyone uploaded an image using Node, React, Antd library to cloudinary? I'm getting an error saying that the file is missing. I'm not sure if I should be sending the entire file object or just the name. I have sent the thumburl (sometimes it works others it doesn't there has to be something I'm doing wrong).
This is my backend
const uploadImage = async (req, res) => {
try {
const result = await cloudinary.uploader.upload(req.body.image, {
public_id: `${Date.now()}`,
resource_type: 'auto' //jpeg or png
});
res.json({
public_id: result.public_id,
url: result.secure_url
});
} catch (error) {
console.log(error);
res.status(400).send(error)
}}
This is my frontend:
const Uploader = () => {
const { token } = useSelector(state => state.user);
const handleChange = ({file}) => {
console.log(file)
axios.post(`${process.env.REACT_APP_API}/uploadImages`, {file}, {
headers:{
authtoken: token
}
})
.then(res => console.log(res))
.catch(err => console.log(err))
};
return (
<Upload
listType="picture"
showUploadList
onChange={handleChange}
>
<Button>Upload</Button>
</Upload>
)
}
export default Uploader
IDK why the onChange triggers 3 times when it has worked I've sent the thumbUrl and it uploads 3 times, I have seen that I can use beforeUpload but I believe this works before uploading. I want to upload, preview the image and sending it to the server and then send it to my Form Component to add it to the values I have there
Anyone who has already uploaded could help or any ideas would be appreciated?
When it comes to the file not uploading properly, I am guessing it is because the res.body.image isn't the actual file. I would look at what the value is there. I would guess you are missing a middleware.
As far as your frontend issue, I'm still a little unclear about what the issue you are having exactly is. For an example of a clean frontend upload I would check out https://codesandbox.io/embed/jq4wl1xjv. You could also consider using the upload widget which will handle the preview as well as give you some easy editing options.
https://cloudinary.com/documentation/upload_widget
I was able to figure it out, indeed there is a need of a middleware, I used formidable on the routes of my backend
router.post('/uploadImages', authCheck, adminCheck, formidable({maxFileSize: 5 * 1024 * 1024}), imageUpload);
And fixed the controller
const imageUpload = async (req, res) =>{
try {
//console.log('req files:', req.files.file.path)
const result = await cloudinary.uploader.upload(req.files.file.path, {
public_id: `${Date.now()}`,
resource_type: 'auto'
});
res.json({
public_id: result.public_id,
url: result.secure_url
})
} catch (error) {
console.log(error)
}
}
As far as the Frontend goes. Ant designs component takes an action that makes the POST to the backend, and it also takes headers if needed (in my case it takes an authtoken for firebase).
When the image is uploaded it makes the POST to the backend based on the "action". The response will bring back the "FileList" with the upload URL to access it.
<Upload
listType="picture"
showUploadList
multiple
action={`${process.env.REACT_APP_API}/uploadImages`}
headers={{authtoken: token}}
>
<Button>Upload Images</Button>
</Upload>
I hope this helps somebody else too
I tried this and it's worked for me.
Here I used Multer middleware for handling form data(for uploading files).
I used this route,
router.post('/uploadImages', authMiddleware, multer.single("image"), imageUpload);
Corrected controller,
try {
let result;
if (req.file) {
result = await cloudinary.uploader.upload(req.files.file.path, {
public_id: `${Date.now()}`,
resource_type: 'auto'
});
}
res.json({
public_id: result.public_id,
url: result.secure_url
})
} catch (error) {
console.log(error)
}
}
//Multer config file,
const multer = require("multer");
const path = require("path");
module.exports = multer({
storage: multer.diskStorage({}),
fileFilter: (req, file, cb) => {
let ext = path.extname(file.originalname);
if (
ext !== ".jpg" &&
ext !== ".jpeg" &&
ext !== ".png" &&
ext !== ".PNG" &&
ext !== ".JPG" &&
ext !== ".JPEG"
) {
cb(new Error("File type is not supported"), false);
return;
}
cb(null, true);
},
});

FormData with NextJS API

Background
I am trying to create a simple CRUD application using NextJS along with react-redux, so what it does is that it saves peoples contacts.So when adding a contact i am trying to send some data along with a file to a NextJS API.
Issue
ContactAction.js
Make a POST request from redux action to add a contact
export const addContact = (data) => async (dispatch) => {
try {
var formData=new FormData();
formData.append('name',data.Name);
formData.append('email',data.Email);
formData.append('phone',data.Phone);
formData.append('image',data.Image);
let response= await Axios.post(`http://localhost:3000/api/contact/addContact`,formData,{
headers:{
'x-auth-token':localStorage.getItem('token')
}
});
} catch (error) {
console.log(error);
}
}
addContact.js
This is the API route in /api/contact/
const handler = async (req, res) => {
switch(req.method){
case "POST":{
await addContact(req,res)
}
}
}
const addContact = async (req, res) => {
console.log(req.body);
// do some stuff here and send response
}
this is what i get in the terminal after the log,also the file is Gibberish as well when logging req.files
Current Effort
I tried using third party packages such as formidable and formidable-serverless but got no luck. so after a day i made it work with a package called multiparty.
addContact.js
const handler = async (req, res) => {
switch(req.method){
case "POST":{
let form = new multiparty.Form();
let FormResp= await new Promise((resolve,reject)=>{
form.parse(req,(err,fields,files)=>{
if(err) reject(err)
resolve({fields,files})
});
});
const {fields,files} = FormResp;
req.body=fields;
req.files=files;
await addContact(req,res)
}
}
}
const addContact = async (req, res) => {
console.log(req.body); //Now i get an Object which i can use
// do some stuff here and send response
}
The above solution is obviously redundant and probably not the best way to go about it plus i don't want to add these 7 8 lines into each route.
so if someone could help me understand what i am doing wrong and why formData doesn't seem to work with NextJS API (when it works with the Express server) i would be grateful.
FormData uses multipart/form-data format. That is not a simple POST request with a body. It is generally used for uploading files, that's why it needs special handling. As an alternative, you could use JSON.
Here is my solution, i hope this helps anybody.
First of all you need to install next-connect and multer as your dependencies.
Now you can use this API route code.
import nextConnect from "next-connect";
import multer from "multer";
const apiRoute = 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` });
},
});
apiRoute.use(multer().any());
apiRoute.post((req, res) => {
console.log(req.files); // Your files here
console.log(req.body); // Your form data here
// Any logic with your data here
res.status(200).json({ data: "success" });
});
export default apiRoute;
export const config = {
api: {
bodyParser: false, // Disallow body parsing, consume as stream
},
};
Here is an example about uploading file with Next.js:
https://codesandbox.io/s/thyb0?file=/pages/api/file.js
The most important code is in pages/api/file.js
import formidable from "formidable";
import fs from "fs";
export const config = {
api: {
bodyParser: false
}
};
const post = async (req, res) => {
const form = new formidable.IncomingForm();
form.parse(req, async function (err, fields, files) {
await saveFile(files.file);
return res.status(201).send("");
});
};
const saveFile = async (file) => {
const data = fs.readFileSync(file.path);
fs.writeFileSync(`./public/${file.name}`, data);
await fs.unlinkSync(file.path);
return;
};
Generally speaking,in your api file,you should disable the default bodyParser,and write your own parser

React PWA - How do you detect if the requested data to be served from cache or from server

I am building a PWA app, where I download data by a user action call, "Go to offline". So once the button is clicked the data is fetched and saved to the browser indexdb storage. I am using workbox and CRA2. I folled this blog post to customize workbox config with cra app.
Now say there is no PWA, and when page requests data, react call actions for example:
export async function fetchProjectStatuses(
dispatch,
params = {},
raiseError = false
) {
try {
dispatch(requestProjectStatuses());
const data = await fetchProjectStatusesApi();
dispatch(receiveProjectStatuses(data));
return data;
} catch (err) {
if (raiseError) {
throw err;
} else {
dispatch(requestProjectStatusesFailed(err));
}
}
}
and fetchProjectStatusesApi is defined as:
import axios from "axios";
const fetchAllUrl = () => `/project_statuses.json`;
export async function fetchProjectStatusesApi(config, params = {}) {
const url = fetchAllUrl();
const { data } = await axios.get(url, {
headers: { Accept: "application/json" }
});
return data;
}
This works. Now when offline, I am trying to write something like:
import { registerRoute } from "workbox-routing";
registerRoute(
new RegExp("/project_statuses\\.json"),
async ({ url, event, params }) => {
try {
const response = await fetch(event.request);
const responseBody = await response.text();
return new Response(responseBody);
} catch (err) {
// get from DB
}
}
);
So how do I write handler so that it forwards the data to the fetchProjectStatusesApi if network is present , else get data from DB. I know how to pull the date from local IDB. Only thing I am not able to figure out:
How do I detect the app is offline so the data has to come from local db
If app is online how do I forward the response from fetch to axios which is called from the api function.
I am writing it first time so I have no idea yet. Any help will be appreciated.
Thank you.

Correct image path to display image properly on front-end in Express React (create-react-app)

The answer to this problem is probably very simple, I hope.
So I'm creating an app based on MongoDB, Node, Express, React and so on, but I can't figure out how to properly setup and display user's avatar after he uploads it.
I have a ready API endpoint for uploading avatar, which when hit with request successfully uploads an image. (with multer middleware help) I'm also storing it in MongoDB properly. The thing is it currently looks like this and I'm pretty sure it shouldn't:
In Redux state:
avatar(pin): "C:\Users\Kuba\Desktop\mern_project\client\public\avatars\uploads\profileAvatar-1558374898723.jpeg"
So how should the path to uploaded image look like so I can successfully display it in React (built with create-react-app if that's of any help). In which folder should I store uploaded images?
That is my default avatar path which works pretty well, but it was simply implemented by import in React component.
avatar(pin): "/static/media/template-avatar2.173d1842.svg"
Project structure image
Client is the frontend, API endpoint in routes/api/profile.js
I appreciate any help.
Below is the route
router.post(
"/avatar",
passport.authenticate("jwt", { session: false }),
upload.single("avatar"),
(req, res) => {
const errors = {};
// Path to the avatar is in req.file.path
if (!req.file.path) {
errors.avatar = "Wrong file format";
return res.status(404).json(errors);
}
const avatarPath = req.file.path;
Profile.findOne({ user: req.user.id })
.then(profile => {
if (profile) {
Profile.findOneAndUpdate(
{ user: req.user.id },
{ $set: { avatar: avatarPath } },
{ new: true }
)
.then(profile => {
res.json(profile);
})
.catch(err => res.json(err));
} else {
errors.noprofile = "Couldn't find the profile";
res.status(404).json(errors);
}
errors.noprofile = "Couldn't find the profile";
})
.catch(err => res.status(404).json(errors));
}
);
Multer settings
const multer = require("multer");
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "./client/src/img/uploads/avatars");
},
filename: (req, file, cb) => {
cb(
null,
file.fieldname + "-" + file.filename + "." + file.mimetype.slice(6)
);
}
});
const fileFilter = (req, file, cb) => {
if (file.mimetype === "image/jpeg" || file.mimetype === "image/png")
cb(null, true);
else {
// reject a file
let errors = {};
cb(
new Error(
(errors.avatar = "Wrong filetype, only png and jpg types are eligible")
),
false
);
}
};
// Upload avatar middleware
const upload = multer({
storage,
limits: {
fileSize: 1024 * 1024 * 2
},
fileFilter
});
EDIT: Okay I changed the path in Multer to relative one, looks like this now, still doesn't work.
avatar(pin): "client\src\img\uploads\avatars\avatar-undefined.png"
Eventually solved this so I'm posting the answer in case someone has the same problem.
Solution is kinda stupid actually but it works so...
Apparently in order to get images to show in create-react-app they must be stored in "client/public" and that's where the root directory starts.
So I changed the destination directory in multer to:
"./client/public/images/uploads/avatars"
And since the root directory starts with public, the path had to start with "/images/..." in my case. So I simply cut the first 15 characters - the whole "./client/public", before storing it in database.

Resources