Upload image file from React front end to Node/Express/Mongoose/MongoDB back end (not working)
I’ve spent most of a day looking into this and trying to make it work. This is an app with a React/Redux front end, and a Node/Express/Mongoose/MongoDB back end.
I currently have a Topics system where an authorized user can follow/unfollow topics, and an admin can Add/Remove topics.
I want to be able to upload an image file when submitting a new topic, and I want to use Cloudinary to store the image and then save the images path to the DB with the topic name.
The problem I am having is that I am unable to receive the uploaded file on the back end from the front end. I end up receiving an empty object, despite tons of research and trial/error. I haven’t finished setting up Cloudinary file upload, but I need to receive the file on the back end before even worrying about that.
SERVER SIDE
index.js:
const express = require("express");
const http = require("http");
const bodyParser = require("body-parser");
const morgan = require("morgan");
const app = express();
const router = require("./router");
const mongoose = require("mongoose");
const cors = require("cors");
const fileUpload = require("express-fileupload");
const config = require("./config");
const multer = require("multer");
const cloudinary = require("cloudinary");
const cloudinaryStorage = require("multer-storage-cloudinary");
app.use(fileUpload());
//file storage setup
cloudinary.config({
cloud_name: "niksauce",
api_key: config.cloudinaryAPIKey,
api_secret: config.cloudinaryAPISecret
});
const storage = cloudinaryStorage({
cloudinary: cloudinary,
folder: "images",
allowedFormats: ["jpg", "png"],
transformation: [{ width: 500, height: 500, crop: "limit" }] //optional, from a demo
});
const parser = multer({ storage: storage });
//DB setup
mongoose.Promise = global.Promise;
mongoose.connect(
`mongodb://path/to/mlab`,
{ useNewUrlParser: true }
);
mongoose.connection
.once("open", () => console.log("Connected to MongoLab instance."))
.on("error", error => console.log("Error connecting to MongoLab:", error));
//App setup
app.use(morgan("combined"));
app.use(bodyParser.json({ type: "*/*" }));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
router(app, parser);
//Server setup
const port = process.env.PORT || 3090;
const server = http.createServer(app);
server.listen(port);
console.log("server listening on port: ", port);
TopicController/CreateTopic
exports.createTopic = function(req, res, next) {
console.log("REQUEST: ", req.body); //{ name: 'Topic with Image', image: {} }
console.log("IMAGE FILE MAYBE? ", req.file); //undefined
console.log("IMAGE FILES MAYBE? ", req.files); //undefined
const topic = new Topic(req.body);
if (req.file) {
topic.image.url = req.file.url;
topic.image.id = req.file.publid_id;
} else {
console.log("NO FILE UPLOADED");
}
topic.save().then(result => {
res.status(201).send(topic);
});
};
router.js
module.exports = function(app, parser) {
//User
app.post("/signin", requireSignin, Authentication.signin);
app.post("/signup", Authentication.signup);
//Topic
app.get("/topics", Topic.fetchTopics);
app.post("/topics/newTopic", parser.single("image"), Topic.createTopic);
app.post("/topics/removeTopic", Topic.removeTopic);
app.post("/topics/followTopic", Topic.followTopic);
app.post("/topics/unfollowTopic", Topic.unfollowTopic);
};
CLIENT SIDE
Topics.js:
import React, { Component } from "react";
import { connect } from "react-redux";
import { Loader, Grid, Button, Icon, Form } from "semantic-ui-react";
import {
fetchTopics,
followTopic,
unfollowTopic,
createTopic,
removeTopic
} from "../actions";
import requireAuth from "./hoc/requireAuth";
import Background1 from "../assets/images/summer.jpg";
import Background2 from "../assets/images/winter.jpg";
const compare = (arr1, arr2) => {
let inBoth = ;
arr1.forEach(e1 =>
arr2.forEach(e2 => {
if (e1 === e2) {
inBoth.push(e1);
}
})
);
return inBoth;
};
class Topics extends Component {
constructor(props) {
super(props);
this.props.fetchTopics();
this.state = {
newTopic: "",
selectedFile: null,
error: ""
};
}
onFollowClick = topicId => {
const { id } = this.props.user;
this.props.followTopic(id, topicId);
};
onUnfollowClick = topicId => {
const { id } = this.props.user;
this.props.unfollowTopic(id, topicId);
};
handleSelectedFile = e => {
console.log(e.target.files[0]);
this.setState({
selectedFile: e.target.files[0]
});
};
createTopicSubmit = e => {
e.preventDefault();
const { newTopic, selectedFile } = this.state;
this.props.createTopic(newTopic.trim(), selectedFile);
this.setState({
newTopic: "",
selectedFile: null
});
};
removeTopicSubmit = topicId => {
this.props.removeTopic(topicId);
};
renderTopics = () => {
const { topics, user } = this.props;
const followedTopics =
topics &&
user &&
compare(topics.map(topic => topic._id), user.followedTopics);
console.log(topics);
return topics.map((topic, i) => {
return (
<Grid.Column className="topic-container" key={topic._id}>
<div
className="topic-image"
style={{
background:
i % 2 === 0 ? `url(${Background1})` : `url(${Background2})`,
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
backgroundSize: "cover"
}}
/>
<p className="topic-name">{topic.name}</p>
<div className="topic-follow-btn">
{followedTopics.includes(topic._id) ? (
<Button
icon
color="olive"
onClick={() => this.onUnfollowClick(topic._id)}
>
Unfollow
<Icon color="red" name="heart" />
</Button>
) : (
<Button
icon
color="teal"
onClick={() => this.onFollowClick(topic._id)}
>
Follow
<Icon color="red" name="heart outline" />
</Button>
)}
{/* Should put a warning safety catch on initial click, as to not accidentally delete an important topic */}
{user.isAdmin ? (
<Button
icon
color="red"
onClick={() => this.removeTopicSubmit(topic._id)}
>
<Icon color="black" name="trash" />
</Button>
) : null}
</div>
</Grid.Column>
);
});
};
render() {
const { loading, user } = this.props;
if (loading) {
return (
<Loader active inline="centered">
Loading
</Loader>
);
}
return (
<div>
<h1>Topics</h1>
{user && user.isAdmin ? (
<div>
<h3>Create a New Topic</h3>
<Form
onSubmit={this.createTopicSubmit}
encType="multipart/form-data"
>
<Form.Field>
<input
value={this.state.newTopic}
onChange={e => this.setState({ newTopic: e.target.value })}
placeholder="Create New Topic"
/>
</Form.Field>
<Form.Field>
<label>Upload an Image</label>
<input
type="file"
name="image"
onChange={this.handleSelectedFile}
/>
</Form.Field>
<Button type="submit">Create Topic</Button>
</Form>
</div>
) : null}
<Grid centered>{this.renderTopics()}</Grid>
</div>
);
}
}
const mapStateToProps = state => {
const { loading, topics } = state.topics;
const { user } = state.auth;
return { loading, topics, user };
};
export default requireAuth(
connect(
mapStateToProps,
{ fetchTopics, followTopic, unfollowTopic, createTopic, removeTopic }
)(Topics)
);
TopicActions/createTopic:
export const createTopic = (topicName, imageFile) => {
console.log("IMAGE IN ACTIONS: ", imageFile); //this is still here
// const data = new FormData();
// data.append("image", imageFile);
// data.append("name", topicName);
const data = {
image: imageFile,
name: topicName
};
console.log("DATA TO SEND: ", data); //still shows image file
return dispatch => {
// const config = { headers: { "Content-Type": "multipart/form-data" } };
// ^ this fixes nothing, only makes the problem worse
axios.post(CREATE_NEW_TOPIC, data).then(res => {
dispatch({
type: CREATE_TOPIC,
payload: res.data
});
});
};
};
When I send it like this, I receive the following on the back end:
(these are server console.logs)
REQUEST: { image: {}, name: 'NEW TOPIC' }
IMAGE FILE MAYBE? undefined
IMAGE FILES MAYBE? undefined
NO FILE UPLOADED
If I go the new FormData() route, FormData is an empty object, and I get this server error:
POST http://localhost:3090/topics/newTopic net::ERR_EMPTY_RESPONSE
export const createTopic = (topicName, imageFile) => {
console.log("IMAGE IN ACTIONS: ", imageFile);
const data = new FormData();
data.append("image", imageFile);
data.append("name", topicName);
// const data = {
// image: imageFile,
// name: topicName
// };
console.log("DATA TO SEND: ", data); // shows FormData {} (empty object, nothing in it)
return dispatch => {
// const config = { headers: { "Content-Type": "multipart/form-data" } };
// ^ this fixes nothing, only makes the problem worse
axios.post(CREATE_NEW_TOPIC, data).then(res => {
dispatch({
type: CREATE_TOPIC,
payload: res.data
});
});
};
};
node.js reactjs express file-upload cloudinary
|
show 6 more comments
I’ve spent most of a day looking into this and trying to make it work. This is an app with a React/Redux front end, and a Node/Express/Mongoose/MongoDB back end.
I currently have a Topics system where an authorized user can follow/unfollow topics, and an admin can Add/Remove topics.
I want to be able to upload an image file when submitting a new topic, and I want to use Cloudinary to store the image and then save the images path to the DB with the topic name.
The problem I am having is that I am unable to receive the uploaded file on the back end from the front end. I end up receiving an empty object, despite tons of research and trial/error. I haven’t finished setting up Cloudinary file upload, but I need to receive the file on the back end before even worrying about that.
SERVER SIDE
index.js:
const express = require("express");
const http = require("http");
const bodyParser = require("body-parser");
const morgan = require("morgan");
const app = express();
const router = require("./router");
const mongoose = require("mongoose");
const cors = require("cors");
const fileUpload = require("express-fileupload");
const config = require("./config");
const multer = require("multer");
const cloudinary = require("cloudinary");
const cloudinaryStorage = require("multer-storage-cloudinary");
app.use(fileUpload());
//file storage setup
cloudinary.config({
cloud_name: "niksauce",
api_key: config.cloudinaryAPIKey,
api_secret: config.cloudinaryAPISecret
});
const storage = cloudinaryStorage({
cloudinary: cloudinary,
folder: "images",
allowedFormats: ["jpg", "png"],
transformation: [{ width: 500, height: 500, crop: "limit" }] //optional, from a demo
});
const parser = multer({ storage: storage });
//DB setup
mongoose.Promise = global.Promise;
mongoose.connect(
`mongodb://path/to/mlab`,
{ useNewUrlParser: true }
);
mongoose.connection
.once("open", () => console.log("Connected to MongoLab instance."))
.on("error", error => console.log("Error connecting to MongoLab:", error));
//App setup
app.use(morgan("combined"));
app.use(bodyParser.json({ type: "*/*" }));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
router(app, parser);
//Server setup
const port = process.env.PORT || 3090;
const server = http.createServer(app);
server.listen(port);
console.log("server listening on port: ", port);
TopicController/CreateTopic
exports.createTopic = function(req, res, next) {
console.log("REQUEST: ", req.body); //{ name: 'Topic with Image', image: {} }
console.log("IMAGE FILE MAYBE? ", req.file); //undefined
console.log("IMAGE FILES MAYBE? ", req.files); //undefined
const topic = new Topic(req.body);
if (req.file) {
topic.image.url = req.file.url;
topic.image.id = req.file.publid_id;
} else {
console.log("NO FILE UPLOADED");
}
topic.save().then(result => {
res.status(201).send(topic);
});
};
router.js
module.exports = function(app, parser) {
//User
app.post("/signin", requireSignin, Authentication.signin);
app.post("/signup", Authentication.signup);
//Topic
app.get("/topics", Topic.fetchTopics);
app.post("/topics/newTopic", parser.single("image"), Topic.createTopic);
app.post("/topics/removeTopic", Topic.removeTopic);
app.post("/topics/followTopic", Topic.followTopic);
app.post("/topics/unfollowTopic", Topic.unfollowTopic);
};
CLIENT SIDE
Topics.js:
import React, { Component } from "react";
import { connect } from "react-redux";
import { Loader, Grid, Button, Icon, Form } from "semantic-ui-react";
import {
fetchTopics,
followTopic,
unfollowTopic,
createTopic,
removeTopic
} from "../actions";
import requireAuth from "./hoc/requireAuth";
import Background1 from "../assets/images/summer.jpg";
import Background2 from "../assets/images/winter.jpg";
const compare = (arr1, arr2) => {
let inBoth = ;
arr1.forEach(e1 =>
arr2.forEach(e2 => {
if (e1 === e2) {
inBoth.push(e1);
}
})
);
return inBoth;
};
class Topics extends Component {
constructor(props) {
super(props);
this.props.fetchTopics();
this.state = {
newTopic: "",
selectedFile: null,
error: ""
};
}
onFollowClick = topicId => {
const { id } = this.props.user;
this.props.followTopic(id, topicId);
};
onUnfollowClick = topicId => {
const { id } = this.props.user;
this.props.unfollowTopic(id, topicId);
};
handleSelectedFile = e => {
console.log(e.target.files[0]);
this.setState({
selectedFile: e.target.files[0]
});
};
createTopicSubmit = e => {
e.preventDefault();
const { newTopic, selectedFile } = this.state;
this.props.createTopic(newTopic.trim(), selectedFile);
this.setState({
newTopic: "",
selectedFile: null
});
};
removeTopicSubmit = topicId => {
this.props.removeTopic(topicId);
};
renderTopics = () => {
const { topics, user } = this.props;
const followedTopics =
topics &&
user &&
compare(topics.map(topic => topic._id), user.followedTopics);
console.log(topics);
return topics.map((topic, i) => {
return (
<Grid.Column className="topic-container" key={topic._id}>
<div
className="topic-image"
style={{
background:
i % 2 === 0 ? `url(${Background1})` : `url(${Background2})`,
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
backgroundSize: "cover"
}}
/>
<p className="topic-name">{topic.name}</p>
<div className="topic-follow-btn">
{followedTopics.includes(topic._id) ? (
<Button
icon
color="olive"
onClick={() => this.onUnfollowClick(topic._id)}
>
Unfollow
<Icon color="red" name="heart" />
</Button>
) : (
<Button
icon
color="teal"
onClick={() => this.onFollowClick(topic._id)}
>
Follow
<Icon color="red" name="heart outline" />
</Button>
)}
{/* Should put a warning safety catch on initial click, as to not accidentally delete an important topic */}
{user.isAdmin ? (
<Button
icon
color="red"
onClick={() => this.removeTopicSubmit(topic._id)}
>
<Icon color="black" name="trash" />
</Button>
) : null}
</div>
</Grid.Column>
);
});
};
render() {
const { loading, user } = this.props;
if (loading) {
return (
<Loader active inline="centered">
Loading
</Loader>
);
}
return (
<div>
<h1>Topics</h1>
{user && user.isAdmin ? (
<div>
<h3>Create a New Topic</h3>
<Form
onSubmit={this.createTopicSubmit}
encType="multipart/form-data"
>
<Form.Field>
<input
value={this.state.newTopic}
onChange={e => this.setState({ newTopic: e.target.value })}
placeholder="Create New Topic"
/>
</Form.Field>
<Form.Field>
<label>Upload an Image</label>
<input
type="file"
name="image"
onChange={this.handleSelectedFile}
/>
</Form.Field>
<Button type="submit">Create Topic</Button>
</Form>
</div>
) : null}
<Grid centered>{this.renderTopics()}</Grid>
</div>
);
}
}
const mapStateToProps = state => {
const { loading, topics } = state.topics;
const { user } = state.auth;
return { loading, topics, user };
};
export default requireAuth(
connect(
mapStateToProps,
{ fetchTopics, followTopic, unfollowTopic, createTopic, removeTopic }
)(Topics)
);
TopicActions/createTopic:
export const createTopic = (topicName, imageFile) => {
console.log("IMAGE IN ACTIONS: ", imageFile); //this is still here
// const data = new FormData();
// data.append("image", imageFile);
// data.append("name", topicName);
const data = {
image: imageFile,
name: topicName
};
console.log("DATA TO SEND: ", data); //still shows image file
return dispatch => {
// const config = { headers: { "Content-Type": "multipart/form-data" } };
// ^ this fixes nothing, only makes the problem worse
axios.post(CREATE_NEW_TOPIC, data).then(res => {
dispatch({
type: CREATE_TOPIC,
payload: res.data
});
});
};
};
When I send it like this, I receive the following on the back end:
(these are server console.logs)
REQUEST: { image: {}, name: 'NEW TOPIC' }
IMAGE FILE MAYBE? undefined
IMAGE FILES MAYBE? undefined
NO FILE UPLOADED
If I go the new FormData() route, FormData is an empty object, and I get this server error:
POST http://localhost:3090/topics/newTopic net::ERR_EMPTY_RESPONSE
export const createTopic = (topicName, imageFile) => {
console.log("IMAGE IN ACTIONS: ", imageFile);
const data = new FormData();
data.append("image", imageFile);
data.append("name", topicName);
// const data = {
// image: imageFile,
// name: topicName
// };
console.log("DATA TO SEND: ", data); // shows FormData {} (empty object, nothing in it)
return dispatch => {
// const config = { headers: { "Content-Type": "multipart/form-data" } };
// ^ this fixes nothing, only makes the problem worse
axios.post(CREATE_NEW_TOPIC, data).then(res => {
dispatch({
type: CREATE_TOPIC,
payload: res.data
});
});
};
};
node.js reactjs express file-upload cloudinary
Open your devtools, network tab and see if the multipart data is available in the latter part of the request (afterRequest Headers
). Please paste that part here
– Masious
Nov 14 '18 at 1:31
Accept: application/json, text/plain, / Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Connection: keep-alive Content-Length: 37 Content-Type: application/json;charset=UTF-8 Host: localhost:3090 Origin: localhost:3000 Referer: localhost:3000/topics User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
– Nik Hammer-Ellis
Nov 14 '18 at 1:56
No, I meant the field after this one. What is there after these headers?
– Masious
Nov 14 '18 at 1:57
" ..this fixes nothing, .." So where exactly did you ever use theconfig
variable? I see nothing in the code or even a commented attempt that shows you using it. See How do I set multipart in axios with react?
– Neil Lunn
Nov 14 '18 at 2:01
Good link, will try that pattern. Ya I had that config variable in the axios post, just deleted instead of commenting out. @Masious just the request payload, as follows: { image: {}, name: "name I submitted" }
– Nik Hammer-Ellis
Nov 14 '18 at 2:06
|
show 6 more comments
I’ve spent most of a day looking into this and trying to make it work. This is an app with a React/Redux front end, and a Node/Express/Mongoose/MongoDB back end.
I currently have a Topics system where an authorized user can follow/unfollow topics, and an admin can Add/Remove topics.
I want to be able to upload an image file when submitting a new topic, and I want to use Cloudinary to store the image and then save the images path to the DB with the topic name.
The problem I am having is that I am unable to receive the uploaded file on the back end from the front end. I end up receiving an empty object, despite tons of research and trial/error. I haven’t finished setting up Cloudinary file upload, but I need to receive the file on the back end before even worrying about that.
SERVER SIDE
index.js:
const express = require("express");
const http = require("http");
const bodyParser = require("body-parser");
const morgan = require("morgan");
const app = express();
const router = require("./router");
const mongoose = require("mongoose");
const cors = require("cors");
const fileUpload = require("express-fileupload");
const config = require("./config");
const multer = require("multer");
const cloudinary = require("cloudinary");
const cloudinaryStorage = require("multer-storage-cloudinary");
app.use(fileUpload());
//file storage setup
cloudinary.config({
cloud_name: "niksauce",
api_key: config.cloudinaryAPIKey,
api_secret: config.cloudinaryAPISecret
});
const storage = cloudinaryStorage({
cloudinary: cloudinary,
folder: "images",
allowedFormats: ["jpg", "png"],
transformation: [{ width: 500, height: 500, crop: "limit" }] //optional, from a demo
});
const parser = multer({ storage: storage });
//DB setup
mongoose.Promise = global.Promise;
mongoose.connect(
`mongodb://path/to/mlab`,
{ useNewUrlParser: true }
);
mongoose.connection
.once("open", () => console.log("Connected to MongoLab instance."))
.on("error", error => console.log("Error connecting to MongoLab:", error));
//App setup
app.use(morgan("combined"));
app.use(bodyParser.json({ type: "*/*" }));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
router(app, parser);
//Server setup
const port = process.env.PORT || 3090;
const server = http.createServer(app);
server.listen(port);
console.log("server listening on port: ", port);
TopicController/CreateTopic
exports.createTopic = function(req, res, next) {
console.log("REQUEST: ", req.body); //{ name: 'Topic with Image', image: {} }
console.log("IMAGE FILE MAYBE? ", req.file); //undefined
console.log("IMAGE FILES MAYBE? ", req.files); //undefined
const topic = new Topic(req.body);
if (req.file) {
topic.image.url = req.file.url;
topic.image.id = req.file.publid_id;
} else {
console.log("NO FILE UPLOADED");
}
topic.save().then(result => {
res.status(201).send(topic);
});
};
router.js
module.exports = function(app, parser) {
//User
app.post("/signin", requireSignin, Authentication.signin);
app.post("/signup", Authentication.signup);
//Topic
app.get("/topics", Topic.fetchTopics);
app.post("/topics/newTopic", parser.single("image"), Topic.createTopic);
app.post("/topics/removeTopic", Topic.removeTopic);
app.post("/topics/followTopic", Topic.followTopic);
app.post("/topics/unfollowTopic", Topic.unfollowTopic);
};
CLIENT SIDE
Topics.js:
import React, { Component } from "react";
import { connect } from "react-redux";
import { Loader, Grid, Button, Icon, Form } from "semantic-ui-react";
import {
fetchTopics,
followTopic,
unfollowTopic,
createTopic,
removeTopic
} from "../actions";
import requireAuth from "./hoc/requireAuth";
import Background1 from "../assets/images/summer.jpg";
import Background2 from "../assets/images/winter.jpg";
const compare = (arr1, arr2) => {
let inBoth = ;
arr1.forEach(e1 =>
arr2.forEach(e2 => {
if (e1 === e2) {
inBoth.push(e1);
}
})
);
return inBoth;
};
class Topics extends Component {
constructor(props) {
super(props);
this.props.fetchTopics();
this.state = {
newTopic: "",
selectedFile: null,
error: ""
};
}
onFollowClick = topicId => {
const { id } = this.props.user;
this.props.followTopic(id, topicId);
};
onUnfollowClick = topicId => {
const { id } = this.props.user;
this.props.unfollowTopic(id, topicId);
};
handleSelectedFile = e => {
console.log(e.target.files[0]);
this.setState({
selectedFile: e.target.files[0]
});
};
createTopicSubmit = e => {
e.preventDefault();
const { newTopic, selectedFile } = this.state;
this.props.createTopic(newTopic.trim(), selectedFile);
this.setState({
newTopic: "",
selectedFile: null
});
};
removeTopicSubmit = topicId => {
this.props.removeTopic(topicId);
};
renderTopics = () => {
const { topics, user } = this.props;
const followedTopics =
topics &&
user &&
compare(topics.map(topic => topic._id), user.followedTopics);
console.log(topics);
return topics.map((topic, i) => {
return (
<Grid.Column className="topic-container" key={topic._id}>
<div
className="topic-image"
style={{
background:
i % 2 === 0 ? `url(${Background1})` : `url(${Background2})`,
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
backgroundSize: "cover"
}}
/>
<p className="topic-name">{topic.name}</p>
<div className="topic-follow-btn">
{followedTopics.includes(topic._id) ? (
<Button
icon
color="olive"
onClick={() => this.onUnfollowClick(topic._id)}
>
Unfollow
<Icon color="red" name="heart" />
</Button>
) : (
<Button
icon
color="teal"
onClick={() => this.onFollowClick(topic._id)}
>
Follow
<Icon color="red" name="heart outline" />
</Button>
)}
{/* Should put a warning safety catch on initial click, as to not accidentally delete an important topic */}
{user.isAdmin ? (
<Button
icon
color="red"
onClick={() => this.removeTopicSubmit(topic._id)}
>
<Icon color="black" name="trash" />
</Button>
) : null}
</div>
</Grid.Column>
);
});
};
render() {
const { loading, user } = this.props;
if (loading) {
return (
<Loader active inline="centered">
Loading
</Loader>
);
}
return (
<div>
<h1>Topics</h1>
{user && user.isAdmin ? (
<div>
<h3>Create a New Topic</h3>
<Form
onSubmit={this.createTopicSubmit}
encType="multipart/form-data"
>
<Form.Field>
<input
value={this.state.newTopic}
onChange={e => this.setState({ newTopic: e.target.value })}
placeholder="Create New Topic"
/>
</Form.Field>
<Form.Field>
<label>Upload an Image</label>
<input
type="file"
name="image"
onChange={this.handleSelectedFile}
/>
</Form.Field>
<Button type="submit">Create Topic</Button>
</Form>
</div>
) : null}
<Grid centered>{this.renderTopics()}</Grid>
</div>
);
}
}
const mapStateToProps = state => {
const { loading, topics } = state.topics;
const { user } = state.auth;
return { loading, topics, user };
};
export default requireAuth(
connect(
mapStateToProps,
{ fetchTopics, followTopic, unfollowTopic, createTopic, removeTopic }
)(Topics)
);
TopicActions/createTopic:
export const createTopic = (topicName, imageFile) => {
console.log("IMAGE IN ACTIONS: ", imageFile); //this is still here
// const data = new FormData();
// data.append("image", imageFile);
// data.append("name", topicName);
const data = {
image: imageFile,
name: topicName
};
console.log("DATA TO SEND: ", data); //still shows image file
return dispatch => {
// const config = { headers: { "Content-Type": "multipart/form-data" } };
// ^ this fixes nothing, only makes the problem worse
axios.post(CREATE_NEW_TOPIC, data).then(res => {
dispatch({
type: CREATE_TOPIC,
payload: res.data
});
});
};
};
When I send it like this, I receive the following on the back end:
(these are server console.logs)
REQUEST: { image: {}, name: 'NEW TOPIC' }
IMAGE FILE MAYBE? undefined
IMAGE FILES MAYBE? undefined
NO FILE UPLOADED
If I go the new FormData() route, FormData is an empty object, and I get this server error:
POST http://localhost:3090/topics/newTopic net::ERR_EMPTY_RESPONSE
export const createTopic = (topicName, imageFile) => {
console.log("IMAGE IN ACTIONS: ", imageFile);
const data = new FormData();
data.append("image", imageFile);
data.append("name", topicName);
// const data = {
// image: imageFile,
// name: topicName
// };
console.log("DATA TO SEND: ", data); // shows FormData {} (empty object, nothing in it)
return dispatch => {
// const config = { headers: { "Content-Type": "multipart/form-data" } };
// ^ this fixes nothing, only makes the problem worse
axios.post(CREATE_NEW_TOPIC, data).then(res => {
dispatch({
type: CREATE_TOPIC,
payload: res.data
});
});
};
};
node.js reactjs express file-upload cloudinary
I’ve spent most of a day looking into this and trying to make it work. This is an app with a React/Redux front end, and a Node/Express/Mongoose/MongoDB back end.
I currently have a Topics system where an authorized user can follow/unfollow topics, and an admin can Add/Remove topics.
I want to be able to upload an image file when submitting a new topic, and I want to use Cloudinary to store the image and then save the images path to the DB with the topic name.
The problem I am having is that I am unable to receive the uploaded file on the back end from the front end. I end up receiving an empty object, despite tons of research and trial/error. I haven’t finished setting up Cloudinary file upload, but I need to receive the file on the back end before even worrying about that.
SERVER SIDE
index.js:
const express = require("express");
const http = require("http");
const bodyParser = require("body-parser");
const morgan = require("morgan");
const app = express();
const router = require("./router");
const mongoose = require("mongoose");
const cors = require("cors");
const fileUpload = require("express-fileupload");
const config = require("./config");
const multer = require("multer");
const cloudinary = require("cloudinary");
const cloudinaryStorage = require("multer-storage-cloudinary");
app.use(fileUpload());
//file storage setup
cloudinary.config({
cloud_name: "niksauce",
api_key: config.cloudinaryAPIKey,
api_secret: config.cloudinaryAPISecret
});
const storage = cloudinaryStorage({
cloudinary: cloudinary,
folder: "images",
allowedFormats: ["jpg", "png"],
transformation: [{ width: 500, height: 500, crop: "limit" }] //optional, from a demo
});
const parser = multer({ storage: storage });
//DB setup
mongoose.Promise = global.Promise;
mongoose.connect(
`mongodb://path/to/mlab`,
{ useNewUrlParser: true }
);
mongoose.connection
.once("open", () => console.log("Connected to MongoLab instance."))
.on("error", error => console.log("Error connecting to MongoLab:", error));
//App setup
app.use(morgan("combined"));
app.use(bodyParser.json({ type: "*/*" }));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
router(app, parser);
//Server setup
const port = process.env.PORT || 3090;
const server = http.createServer(app);
server.listen(port);
console.log("server listening on port: ", port);
TopicController/CreateTopic
exports.createTopic = function(req, res, next) {
console.log("REQUEST: ", req.body); //{ name: 'Topic with Image', image: {} }
console.log("IMAGE FILE MAYBE? ", req.file); //undefined
console.log("IMAGE FILES MAYBE? ", req.files); //undefined
const topic = new Topic(req.body);
if (req.file) {
topic.image.url = req.file.url;
topic.image.id = req.file.publid_id;
} else {
console.log("NO FILE UPLOADED");
}
topic.save().then(result => {
res.status(201).send(topic);
});
};
router.js
module.exports = function(app, parser) {
//User
app.post("/signin", requireSignin, Authentication.signin);
app.post("/signup", Authentication.signup);
//Topic
app.get("/topics", Topic.fetchTopics);
app.post("/topics/newTopic", parser.single("image"), Topic.createTopic);
app.post("/topics/removeTopic", Topic.removeTopic);
app.post("/topics/followTopic", Topic.followTopic);
app.post("/topics/unfollowTopic", Topic.unfollowTopic);
};
CLIENT SIDE
Topics.js:
import React, { Component } from "react";
import { connect } from "react-redux";
import { Loader, Grid, Button, Icon, Form } from "semantic-ui-react";
import {
fetchTopics,
followTopic,
unfollowTopic,
createTopic,
removeTopic
} from "../actions";
import requireAuth from "./hoc/requireAuth";
import Background1 from "../assets/images/summer.jpg";
import Background2 from "../assets/images/winter.jpg";
const compare = (arr1, arr2) => {
let inBoth = ;
arr1.forEach(e1 =>
arr2.forEach(e2 => {
if (e1 === e2) {
inBoth.push(e1);
}
})
);
return inBoth;
};
class Topics extends Component {
constructor(props) {
super(props);
this.props.fetchTopics();
this.state = {
newTopic: "",
selectedFile: null,
error: ""
};
}
onFollowClick = topicId => {
const { id } = this.props.user;
this.props.followTopic(id, topicId);
};
onUnfollowClick = topicId => {
const { id } = this.props.user;
this.props.unfollowTopic(id, topicId);
};
handleSelectedFile = e => {
console.log(e.target.files[0]);
this.setState({
selectedFile: e.target.files[0]
});
};
createTopicSubmit = e => {
e.preventDefault();
const { newTopic, selectedFile } = this.state;
this.props.createTopic(newTopic.trim(), selectedFile);
this.setState({
newTopic: "",
selectedFile: null
});
};
removeTopicSubmit = topicId => {
this.props.removeTopic(topicId);
};
renderTopics = () => {
const { topics, user } = this.props;
const followedTopics =
topics &&
user &&
compare(topics.map(topic => topic._id), user.followedTopics);
console.log(topics);
return topics.map((topic, i) => {
return (
<Grid.Column className="topic-container" key={topic._id}>
<div
className="topic-image"
style={{
background:
i % 2 === 0 ? `url(${Background1})` : `url(${Background2})`,
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
backgroundSize: "cover"
}}
/>
<p className="topic-name">{topic.name}</p>
<div className="topic-follow-btn">
{followedTopics.includes(topic._id) ? (
<Button
icon
color="olive"
onClick={() => this.onUnfollowClick(topic._id)}
>
Unfollow
<Icon color="red" name="heart" />
</Button>
) : (
<Button
icon
color="teal"
onClick={() => this.onFollowClick(topic._id)}
>
Follow
<Icon color="red" name="heart outline" />
</Button>
)}
{/* Should put a warning safety catch on initial click, as to not accidentally delete an important topic */}
{user.isAdmin ? (
<Button
icon
color="red"
onClick={() => this.removeTopicSubmit(topic._id)}
>
<Icon color="black" name="trash" />
</Button>
) : null}
</div>
</Grid.Column>
);
});
};
render() {
const { loading, user } = this.props;
if (loading) {
return (
<Loader active inline="centered">
Loading
</Loader>
);
}
return (
<div>
<h1>Topics</h1>
{user && user.isAdmin ? (
<div>
<h3>Create a New Topic</h3>
<Form
onSubmit={this.createTopicSubmit}
encType="multipart/form-data"
>
<Form.Field>
<input
value={this.state.newTopic}
onChange={e => this.setState({ newTopic: e.target.value })}
placeholder="Create New Topic"
/>
</Form.Field>
<Form.Field>
<label>Upload an Image</label>
<input
type="file"
name="image"
onChange={this.handleSelectedFile}
/>
</Form.Field>
<Button type="submit">Create Topic</Button>
</Form>
</div>
) : null}
<Grid centered>{this.renderTopics()}</Grid>
</div>
);
}
}
const mapStateToProps = state => {
const { loading, topics } = state.topics;
const { user } = state.auth;
return { loading, topics, user };
};
export default requireAuth(
connect(
mapStateToProps,
{ fetchTopics, followTopic, unfollowTopic, createTopic, removeTopic }
)(Topics)
);
TopicActions/createTopic:
export const createTopic = (topicName, imageFile) => {
console.log("IMAGE IN ACTIONS: ", imageFile); //this is still here
// const data = new FormData();
// data.append("image", imageFile);
// data.append("name", topicName);
const data = {
image: imageFile,
name: topicName
};
console.log("DATA TO SEND: ", data); //still shows image file
return dispatch => {
// const config = { headers: { "Content-Type": "multipart/form-data" } };
// ^ this fixes nothing, only makes the problem worse
axios.post(CREATE_NEW_TOPIC, data).then(res => {
dispatch({
type: CREATE_TOPIC,
payload: res.data
});
});
};
};
When I send it like this, I receive the following on the back end:
(these are server console.logs)
REQUEST: { image: {}, name: 'NEW TOPIC' }
IMAGE FILE MAYBE? undefined
IMAGE FILES MAYBE? undefined
NO FILE UPLOADED
If I go the new FormData() route, FormData is an empty object, and I get this server error:
POST http://localhost:3090/topics/newTopic net::ERR_EMPTY_RESPONSE
export const createTopic = (topicName, imageFile) => {
console.log("IMAGE IN ACTIONS: ", imageFile);
const data = new FormData();
data.append("image", imageFile);
data.append("name", topicName);
// const data = {
// image: imageFile,
// name: topicName
// };
console.log("DATA TO SEND: ", data); // shows FormData {} (empty object, nothing in it)
return dispatch => {
// const config = { headers: { "Content-Type": "multipart/form-data" } };
// ^ this fixes nothing, only makes the problem worse
axios.post(CREATE_NEW_TOPIC, data).then(res => {
dispatch({
type: CREATE_TOPIC,
payload: res.data
});
});
};
};
node.js reactjs express file-upload cloudinary
node.js reactjs express file-upload cloudinary
edited Nov 14 '18 at 0:02
Nik Hammer-Ellis
asked Nov 13 '18 at 23:53
Nik Hammer-EllisNik Hammer-Ellis
24929
24929
Open your devtools, network tab and see if the multipart data is available in the latter part of the request (afterRequest Headers
). Please paste that part here
– Masious
Nov 14 '18 at 1:31
Accept: application/json, text/plain, / Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Connection: keep-alive Content-Length: 37 Content-Type: application/json;charset=UTF-8 Host: localhost:3090 Origin: localhost:3000 Referer: localhost:3000/topics User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
– Nik Hammer-Ellis
Nov 14 '18 at 1:56
No, I meant the field after this one. What is there after these headers?
– Masious
Nov 14 '18 at 1:57
" ..this fixes nothing, .." So where exactly did you ever use theconfig
variable? I see nothing in the code or even a commented attempt that shows you using it. See How do I set multipart in axios with react?
– Neil Lunn
Nov 14 '18 at 2:01
Good link, will try that pattern. Ya I had that config variable in the axios post, just deleted instead of commenting out. @Masious just the request payload, as follows: { image: {}, name: "name I submitted" }
– Nik Hammer-Ellis
Nov 14 '18 at 2:06
|
show 6 more comments
Open your devtools, network tab and see if the multipart data is available in the latter part of the request (afterRequest Headers
). Please paste that part here
– Masious
Nov 14 '18 at 1:31
Accept: application/json, text/plain, / Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Connection: keep-alive Content-Length: 37 Content-Type: application/json;charset=UTF-8 Host: localhost:3090 Origin: localhost:3000 Referer: localhost:3000/topics User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
– Nik Hammer-Ellis
Nov 14 '18 at 1:56
No, I meant the field after this one. What is there after these headers?
– Masious
Nov 14 '18 at 1:57
" ..this fixes nothing, .." So where exactly did you ever use theconfig
variable? I see nothing in the code or even a commented attempt that shows you using it. See How do I set multipart in axios with react?
– Neil Lunn
Nov 14 '18 at 2:01
Good link, will try that pattern. Ya I had that config variable in the axios post, just deleted instead of commenting out. @Masious just the request payload, as follows: { image: {}, name: "name I submitted" }
– Nik Hammer-Ellis
Nov 14 '18 at 2:06
Open your devtools, network tab and see if the multipart data is available in the latter part of the request (after
Request Headers
). Please paste that part here– Masious
Nov 14 '18 at 1:31
Open your devtools, network tab and see if the multipart data is available in the latter part of the request (after
Request Headers
). Please paste that part here– Masious
Nov 14 '18 at 1:31
Accept: application/json, text/plain, / Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Connection: keep-alive Content-Length: 37 Content-Type: application/json;charset=UTF-8 Host: localhost:3090 Origin: localhost:3000 Referer: localhost:3000/topics User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
– Nik Hammer-Ellis
Nov 14 '18 at 1:56
Accept: application/json, text/plain, / Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Connection: keep-alive Content-Length: 37 Content-Type: application/json;charset=UTF-8 Host: localhost:3090 Origin: localhost:3000 Referer: localhost:3000/topics User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
– Nik Hammer-Ellis
Nov 14 '18 at 1:56
No, I meant the field after this one. What is there after these headers?
– Masious
Nov 14 '18 at 1:57
No, I meant the field after this one. What is there after these headers?
– Masious
Nov 14 '18 at 1:57
" ..this fixes nothing, .." So where exactly did you ever use the
config
variable? I see nothing in the code or even a commented attempt that shows you using it. See How do I set multipart in axios with react?– Neil Lunn
Nov 14 '18 at 2:01
" ..this fixes nothing, .." So where exactly did you ever use the
config
variable? I see nothing in the code or even a commented attempt that shows you using it. See How do I set multipart in axios with react?– Neil Lunn
Nov 14 '18 at 2:01
Good link, will try that pattern. Ya I had that config variable in the axios post, just deleted instead of commenting out. @Masious just the request payload, as follows: { image: {}, name: "name I submitted" }
– Nik Hammer-Ellis
Nov 14 '18 at 2:06
Good link, will try that pattern. Ya I had that config variable in the axios post, just deleted instead of commenting out. @Masious just the request payload, as follows: { image: {}, name: "name I submitted" }
– Nik Hammer-Ellis
Nov 14 '18 at 2:06
|
show 6 more comments
1 Answer
1
active
oldest
votes
Solution was to switch to using Firebase instead, and deal with image upload on the React client (this was attempted with cloudinary but with no success). The resulting download url can be saved to the database with the topic name (which is all I wanted from cloudinary) and now it is displaying the correct images along with the topics.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53291182%2fupload-image-file-from-react-front-end-to-node-express-mongoose-mongodb-back-end%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
Solution was to switch to using Firebase instead, and deal with image upload on the React client (this was attempted with cloudinary but with no success). The resulting download url can be saved to the database with the topic name (which is all I wanted from cloudinary) and now it is displaying the correct images along with the topics.
add a comment |
Solution was to switch to using Firebase instead, and deal with image upload on the React client (this was attempted with cloudinary but with no success). The resulting download url can be saved to the database with the topic name (which is all I wanted from cloudinary) and now it is displaying the correct images along with the topics.
add a comment |
Solution was to switch to using Firebase instead, and deal with image upload on the React client (this was attempted with cloudinary but with no success). The resulting download url can be saved to the database with the topic name (which is all I wanted from cloudinary) and now it is displaying the correct images along with the topics.
Solution was to switch to using Firebase instead, and deal with image upload on the React client (this was attempted with cloudinary but with no success). The resulting download url can be saved to the database with the topic name (which is all I wanted from cloudinary) and now it is displaying the correct images along with the topics.
answered Nov 14 '18 at 21:30
Nik Hammer-EllisNik Hammer-Ellis
24929
24929
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53291182%2fupload-image-file-from-react-front-end-to-node-express-mongoose-mongodb-back-end%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Open your devtools, network tab and see if the multipart data is available in the latter part of the request (after
Request Headers
). Please paste that part here– Masious
Nov 14 '18 at 1:31
Accept: application/json, text/plain, / Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Connection: keep-alive Content-Length: 37 Content-Type: application/json;charset=UTF-8 Host: localhost:3090 Origin: localhost:3000 Referer: localhost:3000/topics User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
– Nik Hammer-Ellis
Nov 14 '18 at 1:56
No, I meant the field after this one. What is there after these headers?
– Masious
Nov 14 '18 at 1:57
" ..this fixes nothing, .." So where exactly did you ever use the
config
variable? I see nothing in the code or even a commented attempt that shows you using it. See How do I set multipart in axios with react?– Neil Lunn
Nov 14 '18 at 2:01
Good link, will try that pattern. Ya I had that config variable in the axios post, just deleted instead of commenting out. @Masious just the request payload, as follows: { image: {}, name: "name I submitted" }
– Nik Hammer-Ellis
Nov 14 '18 at 2:06