Uploading React - Node image(s) to Cloudinary - reactjs

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

Related

Upload file with React

I want to make a simple file upload form on the front end. Then, on the backend, I would pass the information about that file to an API.
Here is my front-end code where I call a specific function on the back end and pass the data:
import React from 'react';
import Axios from 'axios';
const Upload = () => {
// a local state to store the currently selected file.
const [selectedFile, setSelectedFile] = React.useState(null);
const handleSubmit = async (event) => {
event.preventDefault()
//Got all the Infos about my file
console.log(selectedFile)
const formData = new FormData();
formData.append("selectedFile", selectedFile);
//Empty result
console.log(formData)
Axios.get("http://localhost:3001/upload", {
//I will pass the data to a function in the backend
params: {
data: formData,
},
})
.then((Response) => {
console.log(Response)
})
.catch(function (error) {
console.log(error);
});
}
const handleFileSelect = (event) => {
setSelectedFile(event.target.files[0])
}
return (
<form onSubmit={handleSubmit}>
<input type="file" onChange={handleFileSelect}/>
<input type="submit" value="Upload File" />
</form>
)
};
export default Test
On the back-end side, a route call the method
router.get('/upload?', Upload);
Then finally the function in the backend to process
const ApiProcess = (req, res) => {
var axios = require('axios');
var data = req.query
console.log(req.query)
//All the API Stuff
}
But the problem is that I receive empty data in the Backend. What's wrong with my code?
Thanks
EDIT
On backend side I use multer and add 'app.use(multer().any())' on top of index file. That help cause now I cant access in backend to a simple formData. Now my function that receive the data log this '[Object: null prototype] {}'
Any idea ?
This is because your file is not getting forwarded from frontend
use FileReader instead
<input type="submit" value="Upload File" onChange={(e) =>
setFile(e.target.files)} />
const data = new FormData();
data.append(file[0])
and then you can access the file data on file[0] index and after storing the data you can forward it to the backend
there are some problems in your code.
first of all an upload request usually is a post type. and also you should send Content-Type header with your request. so:
Axios.post("http://localhost:3001/upload", formData {
headers: {
'Content-Type': 'Multipart/formData',
},
})
when you log formData it's always empty. you can use some methods like formData.keys() or formData.values() to see inside it.
Ok I got the solution. I missed a piece of middleware to process Multipart/formdata on Express Side :
const router = express.Router();
const multer = require("multer");
//Set the destination folder and Naming of the file that is upload
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/')
},
filename: function (req, file, cb) {
cb(null, file.originalname)
}
})
const upload = multer({ storage: storage })
Then I process the formData with the files
router.post('/upload', upload.array("file"),Upload);
Thanks a lot for your help

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-player uploading and playing new video

I am uploading video through express server to react's public folder and using hook for the name of the file. If its a large video file, after uploading react player starts playing it correctly. But if the file is small, player doesn't run it and I have to reload after some time then it works.
Also if a small file is uploaded after a large file it returns an error range not satisfiable.
React-player:
<ReactPlayer
url={
filePath
}
class="react-player" width="100%"
ref={ref}
controls={true}
onDuration={setDuration}
/>
Axios connection:
useEffect(() => {
const request = () => {
if (serverPath === 'http://localhost:5000/upload') {
const datatoSend = new FormData()
datatoSend.append('file', myFile)
const fetchdata = async () => await axios.post(serverPath, datatoSend, {
onUploadProgress: ProgressEvent => {
setLoaded(ProgressEvent.loaded / ProgressEvent.total * 100)
}
})
const result = fetchdata()
result.then(res => {
if (res.data ==='Server Connected') {
setFilePath('CurrentMedia.mp4')
}
})
}
}
}, [serverPath])
Express Code:
app.post("/upload", async (req, res) => {
console.log('/ route called')
const file = req.files.file
await file.mv(`${__dirname}/client/public/CurrentMedia.mp4`, (err) => {
if (err) {
console.error(err);
return res.status(500).send(err);
}
res.send('Server Connected');
});
})
I was able to solve it by saving the file by a different name every time new file is uploaded. The problem was with react-player trying to run a new video file with the same name. Since the name of the file was same it ran the one it had cached before.
just doing this in express and updating hooks in react respectively fixed it
app.post("/upload", async (req, res) => {
console.log('/ route called')
const file = req.files.file
const name = file.name
file.mv(`${__dirname}/public/${name}`, (err) => {
if (err) {
console.error(err);
return res.status(500).send(err);
}
res.send('Server Connected');
console.log('Server Connected')
});
})

Multer req.file is always undefined despite file object created

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.

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