Upload image file from React front end to Node/Express/Mongoose/MongoDB back end (not working)












1















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









share|improve this question

























  • 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
















1















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









share|improve this question

























  • 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














1












1








1








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









share|improve this question
















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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 (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



















  • 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

















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












1 Answer
1






active

oldest

votes


















0














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.






share|improve this answer























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


    }
    });














    draft saved

    draft discarded


















    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









    0














    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.






    share|improve this answer




























      0














      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.






      share|improve this answer


























        0












        0








        0







        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.






        share|improve this answer













        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.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 14 '18 at 21:30









        Nik Hammer-EllisNik Hammer-Ellis

        24929




        24929






























            draft saved

            draft discarded




















































            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.




            draft saved


            draft discarded














            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





















































            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







            Popular posts from this blog

            Florida Star v. B. J. F.

            Danny Elfman

            Lugert, Oklahoma