I have used multer on my Node Js backend to upload files from my React frontend. I have been storing the files in the React public folder. I have been saving the image path in my MonogoDB database. The idea was use the image path to insert the image into my React frontend. The Account page makes a GET request to the backend retrieves the path however I can't get the image to display. The path when inspected with Dev tools 'http://localhost:3000/user/account/frontend/public/uploads/1621968408663.jpg'. The path sent from the GET request is '../frontend/public/uploads/1621968408663.jpg'. Am I going about this right way? What is the solution.
AccountPage.js
import React, {useEffect, useState} from "react";
import { Link, useParams } from "react-router-dom";
import Header from "./Header";
import axios from "axios";
import SettingsIcon from "@material-ui/icons/Settings";
import IconButton from "@material-ui/core/IconButton";
export default function AccountPage() {
// Declare a new state variable, which we'll call "count"
const { id } = useParams();
const api = `http://localhost:5000/user/account/${id}`;
const [ firstName, setFirstName ] = useState("");
const [ lastName, setLastName ] = useState("");
const [ emailAddress, setEmailAddress ] = useState("");
const [ gender, setGender ] = useState("");
const [ sexualPreference, setSexualPreference ] = useState("");
const [ age, setAge ] = useState("");
const [ description, setDescription ] = useState("");
const [ matches, setMatches ] = useState([{}]);
const [file, setFiles] = useState("")
useEffect(() => {
axios.get(api, {
headers: {
Authorization: localStorage.getItem("jwt"),
"Content-Type": "application/json",
"Cache-Control": "no-cache",
},
})
.then((res) => {
setFirstName(res.data.user.firstName)
setLastName(res.data.user.lastName)
setEmailAddress(res.data.user.emailAddress)
setGender(res.data.user.gender)
setSexualPreference(res.data.user.sexualPreference)
setAge(res.data.user.age)
setDescription(res.data.user.description)
setMatches(res.data.user.matches)
setFiles(res.data.user.path)
});
}, []);
console.log(file)
return (
<div>
<Header />
<div>
<img src={file}/>
<p>{firstName} {lastName}</p>
<p>{emailAddress}</p>
<p>{gender}</p>
<p>{sexualPreference}</p>
<p>{age}</p>
<p>{description}</p>
</div>
<Link to={`/user/settings/${id}`}><IconButton><SettingsIcon className="Header-icon" fontSize="large"/></IconButton></Link>
</div>
);
}
app.js
require("dotenv").config();
const express = require('express');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const path = require('path');
const enableGlobalErrorLogging = process.env.ENABLE_GLOBAL_ERROR_LOGGING === 'true';
const app = express();
const corsOptions ={
origin:'http://localhost:3000',
credentials:true, //access-control-allow-credentials:true
optionSuccessStatus:200
}
app.use(cors(corsOptions));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(cookieParser());
app.use('/uploads', express.static(path.join(__dirname, 'uploads')))
const mongoose = require('mongoose');
const connection = "password";
mongoose.connect(connection, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true,
useFindAndModify: false
});
const userRoutes = require('./routes/userRoutes');
app.use('/', userRoutes);
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Authorization, Content-Type, Accept");
next();
});
// setup a friendly greeting for the root route
app.get('/', (req, res) => {
res.json({
message: 'Welcome to the REST API for Tinder!',
});
});
// send 404 if no other route matched
app.use((req, res) => {
res.status(404).json({
message: 'Route Not Found',
});
});
// setup a global error handler
app.use((err, req, res, next) => {
if (enableGlobalErrorLogging) {
console.error(`Global error handler: ${JSON.stringify(err.stack)}`);
}
res.status(err.status || 500).json({
message: err.message,
error: {},
});
});
app.listen(5000, () => console.log('Listening on port 5000!'))
userRoutes.js
require("dotenv").config();
const express = require("express");
const router = express.Router({ mergeParams: true });
const jwt = require("jsonwebtoken");
const bcryptjs = require("bcryptjs");
const cookieParser = require('cookie-parser');
const { check, validationResult } = require("express-validator");
const multer = require('multer');
const User = require("../models/userSchema");
const ObjectID = require('mongodb').ObjectID;
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/')
},
filename: function (req, file, cb) {
cb(null, Date.now() + '.jpg')
}
})
const upload = multer({ storage: storage })
function asyncHandler(callback) {
return async (req, res, next) => {
try {
await callback(req, res, next);
} catch (error) {
next(error);
console.log(error);
}
};
}
router.post( "/user/create-account", upload.single("file"), [
check("firstName")
.exists({ checkNull: true, checkFalsy: true })
.withMessage('Please provide a value for "firstName"'),
check("lastName")
.exists({ checkNull: true, checkFalsy: true })
.withMessage('Please provide a value for "username"'),
check("emailAddress")
.exists({ checkNull: true, checkFalsy: true })
.withMessage('Please provide a value for "emailAddress"'),
check("password")
.exists({ checkNull: true, checkFalsy: true })
.withMessage('Please provide a value for "password"'),
check("gender")
.exists({ checkNull: true, checkFalsy: true })
.withMessage('Please provide a value for "gender"'),
check("sexualPreference")
.exists({ checkNull: true, checkFalsy: true })
.withMessage('Please provide a value for "sexualPreference"'),
check("age")
.exists({ checkNull: true, checkFalsy: true })
.withMessage('Please provide a value for "age"'),
check("description")
.exists({ checkNull: true, checkFalsy: true })
.withMessage('Please provide a value for "description"'),
],
asyncHandler(async (req, res, next) => {
// Attempt to get the validation result from the Request object.
const errors = validationResult(req);
// If there are validation errors...
if (!errors.isEmpty()) {
// Use the Array `map()` method to get a list of error messages.
const errorMessages = errors.array().map((error) => error.msg);
// Return the validation errors to the client.
return res.status(400).json({ errors: errorMessages });
}
const {file, body: { firstName, lastName, emailAddress, password, gender, sexualPreference, age, description}} = req;
console.log(firstName, lastName, emailAddress, password, gender, sexualPreference, age, description, file);
//new user request body using mongo model from schema
const postUser = new User({
firstName: firstName,
lastName: lastName,
emailAddress: emailAddress,
password: password,
gender: gender,
sexualPreference: sexualPreference,
age: age,
description: description,
file: file,
path: req.file.path
});
const userEmail = await User.findOne({
emailAddress: postUser.emailAddress,
});
if (postUser.emailAddress === userEmail) {
console.log("User with this email already exists");
return res.status(500).end();
} else if (postUser) {
//if true salts the password with bcryptjs
let salt = await bcryptjs.genSalt(10);
const hashPass = await bcryptjs.hash(postUser.password, salt);
postUser.password = hashPass;
postUser.save();
res.json({ postUser });
return res.status(201).end();
} else {
res.status(400).send({ error: "Error: Account not created" }).end();
}
})
);
The image url needs point to a location where the image is served by the backend. You probably don't see the image when you navigate to http://localhost:3000/user/account/frontend/public/uploads/1621968408663.jpg
because it doesn't exist at that location.
First, localhost:3000
is your frontend so you need to change that to point to your backend: localhost:5000
.
Second, you're serving the uploads folder at the /uploads
route so everything inside that folder will be available at http://localhost:5000/uploads/...
. Therefore you should see the image if you navigate to http://localhost:5000/uploads/1621968408663.jpg
.
So we need to go from:
http://localhost:3000/user/account/frontend/public/uploads/1621968408663.jpg
to:
http://localhost:5000/uploads/1621968408663.jpg
.
When you save the user in userRoutes.js
you set User.path
to req.file.path
which ends up being uploads/1621968408663.jpg
. So far so good.
In AccountPage.js
you set image source with <img src={file}/>
which is essentially <img src="uploads/1621968408663.jpg"/>
. This is where things go wrong. Since this is a relative url, the string is appended to URL on the current page.
To fix the issue, change the image source to:
<img src={`http://localhost:5000/${file}`} />