Pass an image saved in state to Express JS as static image - javascript

I have a view that accepts a file (an image, for now) and previews it on the page.
The same view also has a form that accepts two text inputs. Currently, when submitted, the text inputs are sent, via axios, to my Express JS server to be displayed on a different view. I'd like the image that is chosen by the user to, also, be sent to the server and saved statically in a folder there (I'm not using a database yet).
My current attempt simply passes the same state the user image is in to the axios post request. However, this does not work and the view reloads and clears all data. I've not noticed any errors in the console.
How can I achieve my goal?
function sendVideo() {
axios
.post(`${BASE_URL}videos`, {
title: document.getElementById("uploadTitle").value,
description: document.getElementById("uploadDesc").value,
image: this.state.file,
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
class VideoUpload extends React.Component {
constructor(props) {
super(props);
this.state = {
file: null,
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
file: URL.createObjectURL(event.target.files[0]),
});
}
render() {
return (
<section className="uploadContainer">
<section className="uploadContainer__titleContainer">
<h2 className="uploadContainer__title">Upload Video</h2>
</section>
<section className="uploadContainer__detailsContainer">
<input id="qwe" type="file" onChange={this.handleChange} />
<img
type="file"
id="uploadImage"
src={this.state.file}
alt={this.state.file}
className="uploadContainer__thumbnail"
></img>
<div className="uploadContainer__formContainer">
<form action="" className="uploadContainer__form" id="uploadForm">
<h6 className="uploadContainer__text">TITLE YOUR VIDEO</h6>
<input
required
id="uploadTitle"
type="text"
className="uploadContainer__uploadTitle"
placeholder="Add a title to your video"
/>
<h6 className="uploadContainer__text">ADD A VIDEO DESCRIPTION</h6>
<input
required
id="uploadDesc"
type="text"
className="uploadContainer__uploadDesc"
placeholder="Add a description to your video"
/>
</form>
</div>
</section>
<section className="uploadContainer__buttonSection">
<div className="uploadContainer__cancelButton">CANCEL</div>
<button
form="uploadForm"
type="submit"
className="uploadContainer__uploadButton"
onClick={() => sendVideo()}
>
<img
className="uploadContainer__uploadIcon"
draggable="false"
src={uploadIcon}
alt="upload-icon"
/>
PUBLISH
</button>
</section>
</section>
);
}
}
Backend:
const router = require("express").Router();
const path = require("path");
const videoJsonFileName = path.join(__dirname, "../data/videos.json");
const videos = require(videoJsonFileName);
const utils = require("../utils/utils");
router.get("/", (_req, res) => {
res.status(200).json(videos);
});
router.get("/:id", (req, res) => {
const foundVideo = videos.find((video) => video.id === req.params.id);
if (!foundVideo) {
res
.status(404)
.json({ errorMessage: `Video with ID "${req.params.id}" was not found` });
}
res.status(200).json(foundVideo);
});
//CREATE A NEW OBJECT
router.post("/", (req, res) => {
//video title, desc, thumbnail
console.log(req.body);
if (!req.body.title || !req.body.description || !req.body.image)
return res.status(400).json({
errorMessage: "Please provde title, description, and image",
});
const newVideoObj = {
title: req.body.title,
image: req.body.image,
description: req.body.description,
id: utils.getNewId(),
};
utils.writeToJsonFile(videoJsonFileName, [...videos, newVideoObj]),
res.status(201).json({ newVideoCreated: newVideoObj });
});
module.exports = router;

In your frontend:
//formData request body is used to handle file upload
const fd = new FormData();
//the file itself should be appended before any other value.
//file should be of type file or buffer
fd.append(<property name>, <propValue>);
//repeat above line for any additional fields, e.g. name, description etc.
axios.post(url, fd, [...options])
In your backend:
Firstly you'll need to install and require a module that handles multipart, such as multer.
your expressjs method will look something like this:
var storage = multer.memoryStorage();
var upload = multer({ storage: storage });
router.post(<your-url>, upload.single("file"), <your handle function>...)
References: formData (mdn), express-multer

Related

multer + socket.io + FormData sending file issue

I'm trying to make sending pictures with socket.io and bumped into this error
return req.headers['transfer-encoding'] !== undefined ||
TypeError: Cannot read property 'transfer-encoding' of undefined
Before I used axios + multer, it worked perfect
So there is my code
server:
const fileSchema = new mongoose.Schema({
originalname: String,
filename: String,
path: String,
mimetype: String,
});
const File = mongoose.model("File", fileSchema);
socket.on("ROOM:FILE_UPLOAD", (formData) => {
upload.single("image")(formData, null, (err) => {
if (err) console.log(err);
else {
const originalname = formData.originalname;
const filename = formData.filename;
const path = formData.path;
const mimetype = formData.mimetype;
const file = new File({
originalname,
filename,
path,
mimetype,
});
file.save((err, image) => {
if (err) {
console.error(err);
} else {
console.log(`Image saved to MongoDB: ${image._id}`);
}
});
}
});
});
client:
const [file, setFile] = useState(null);
function handleFileSelect(event: any) {
setFile(event.target.files[0]);
}
function sendMsg(event) {
event.preventDefault();
const formData = new FormData();
formData.append("image", file);
socket.emit("ROOM:FILE_UPLOAD", formData);
}
return (
<div className="container">
<div className="row">
<form encType="multipart/form-data" onSubmit={upload}>
<input
type="file"
accept="image/*"
onChange={handleFileSelect}
name="image"
/>
<button onClick={sendMsg}>send</button>
</form>
</div>
</div>
);
where I made mistake ?
So, it was a bad idea from start to send pictures as message via sockets, it would be big server load, so I would use another algorithm similar to telegram
there is the link on docs. There is no any reason to try fix this error because the main mistake was decision how to send pictures in chat

Passing variable from app.js to handlebars template

So I am creating a quote request contact form on a website and I need to make a confirmation or error message pop up under the form after it has been submitted. The issue I am faced with is how can I set a variable on the express side based on whether there was an error or not with the email sending and then use that variable within my handlebars template in order to display the proper message. I'm thinking I would use a helper to achieve this but I keep hitting a wall on attempting it. The logic should begin withing the transporter.sendMail as that is where the error will be determined. I put comments in to help identify.
Here is the backend of my contact form:
// ==== Contact Form ====
//Create Mailer options
const options = {
viewEngine: {
extname: '.hbs',
layoutsDir: __dirname + '/views/email/',
defaultLayout: 'template',
partialsDir: 'views/partials/'
},
viewPath: 'views/email/',
extName: '.hbs'
};
// Create Transporter
const transporter = nodemailer.createTransport({
host: 'smtp-mail.outlook.com',
port: 587,
auth: {
user: process.env.USER,
pass: process.env.PASS
}
});
// verify connection configuration
transporter.verify(function(error, success) {
if (error) {
console.log('Error with transporter verification:' + `\n${error}`);
}
});
//attach the plugin to the nodemailer transporter
transporter.use('compile', hbs(options));
app.post('/send', (req, res) => {
// Accepts the form data submitted and parse it
let form = new multiparty.Form();
let data = {};
form.parse(req, function(err, fields) {
Object.keys(fields).forEach(function(property) {
data[property] = fields[property].toString();
});
// Create Mail object with options
const mail = {
from: `"********" <${process.env.USER}>`,
to: '************', // receiver email,
subject: 'Quote Request',
template: 'email.body',
// Import variables into email for use with handlebars
context: {
name: data.name,
email: data.email,
number: data.number,
message: data.message
}
};
// Send email
transporter.sendMail(mail, (err, data) => {
if (err) {
console.log(err);
// if error return mailError = true;
}
else {
console.log('Email successfully sent to recipient!');
// if sent return mailSent = true;
}
});
});
});
Here is my script.js:
// Contact Form Client Functions
//get the form by its id
const form = document.getElementById('contact-form');
//add event listener (when clicking the submit button, do the following)
const formEvent = form.addEventListener('submit', (event) => {
// Prevent page from refreshing when submit button clicked
event.preventDefault();
//parse data to formData variable
let mail = new FormData(form);
//send mail
sendMail(mail);
// Determine if sendMail returned an error or not
console.log(typeof mailError);
// reset form feilds to empty
form.reset();
});
const sendMail = (mail) => {
console.log('step 1');
fetch('/send', {
method: 'post',
body: mail
}).then((response) => {
return response.json();
});
};
and here is the section within my template.hbs file that I need dynamically updated:
<div>
{{#if mailSent}}
<h4 style="color: lightgreen">Your message has been sent successfully!</h4>
{{else if mailError}}
<h4 style="color: red">ERROR: There was an issue sending your message, please
try again.</h4>
{{/if}}
</div>
I think you are mixing Server Side Rendering vs Client Side Rendering strategies (I suggest you to read this to understand the difference). Typically you'd want to use one or the other.
Server Side Rendering Approach: Here is a quick StackBlitz example I did based on your code using server side rendering that you can play with. The basic idea with this strategy is to let your express route render the response (using Handlebars):
app.post('/send-email', (req, res) => {
// proceed to send email
sendEmail((err, data) => {
// render view based on response:
res.render('form', {
sent: !err,
message: err?.message,
});
});
});
Notice how res.render is used in this case, we are not sending a JSON response but the direct view result instead, which would look something like this:
<form action="/send-email" method="POST">
<h1>Send Email</h1>
<p>Click send to get a random response!</p>
<input type="email" placeholder="Enter your email" value="test#mail.com" required />
<input type="submit" value="Send" />
</form>
<div class="msg">
{{#if sent}}
<h4 style="color: lightgreen">Your message has been sent successfully!</h4>
{{else if message}}
<h4 style="color: red">
ERROR: There was an issue sending your message, please try again.
<br />
Original server error: {{message}}
</h4>
{{/if}}
</div>
<script>
document.querySelector('form').addEventListener('submit', () => {
document.querySelector('.msg').style.display = 'none';
});
</script>
Notice also how we don't use Javascript here to send the request, just the default behavior of <form> to make the request. This will cause the page to reload.
Client Side Rendering Approach: Here is the same example slightly modified to use AJAX and fetch API.
Now our endpoint must return a JSON response that the client can use to react accordingly:
app.post('/send-email', (req, res) => {
sendEmail((err, data) => {
res.status(!err ? 200 : 500).json({
sent: !err,
message: err?.message,
});
});
});
Then we let the client side Javascript handle the request and subsequent update of the DOM:
<form>
<h1>Send Email</h1>
<p>Click send to get a random response!</p>
<input type="email" name="email" placeholder="Enter your email" value="test#mail.com" required />
<input type="submit" value="Send" />
</form>
<div class="msg">
<h4 class="feedback"></h4>
</div>
<script>
function sendMail(mail) {
return fetch('/send-email', {
method: 'post',
body: mail,
}).then(function (response) {
return response.json();
});
}
var msgContainer = document.querySelector('div.msg');
msgContainer.style.display = 'none';
document.querySelector('form').addEventListener('submit', function (e) {
e.preventDefault();
msgContainer.style.display = 'none';
var mail = new FormData(e.target);
sendMail(mail).then(function (res) {
var message = res.sent
? 'Your message has been sent successfully!'
: 'ERROR: There was an issue sending your message, please try again.';
var feedback = document.querySelector('h4.feedback');
feedback.textContent = message;
feedback.style.color = res.sent ? 'lightgreen' : 'red';
msgContainer.style.display = 'block';
});
});
</script>
This will NOT cause the page to reload.

Express HTML patch form req.body is undefined

I'm currently making a to-do list using Express and Node.js with MongoDB and Mongoose. I'm trying to update the to-do list so I've tried to use patch however this just changes the data to null. (No error messages shown but the HTTP status code is 400)
router.patch('/:id', ensureAuth, async(req, res) => {
try {
const id = req.params.id;
const updatedTask = await Task.findByIdAndUpdate(id, {
task: req.body.taskbox
})
res.send(result);
} catch (err) {
res.status(400).json({
message: err.message
})
}
})
I'm thinking the error might be to do with the "req.body.taskbox" since console.log(req.body.taskbox) shows "undefined" and console.log(req.body) prints {}. Furthermore, when I hardcode something there, the patch works. (e.g. task: "Clean window" - this would update the task successfully to say "Clean window").
The code below shows where the HTML form is generated:
static addTaskToTable(currentTask) {
const list = document.querySelector('#task-list');
const row = document.createElement('div');
row.innerHTML = `
<div class="row">
<div class="col-md-8">
<form id="task-form-for-${currentTask.taskId}">
<input type="text" name="taskbox" value="${currentTask.task}" />
</form>
</div>
<div class="col">
<form id="edit-for-${currentTask.taskId}">
<p><input type="submit" class="btn btn-sm btn-primary" value="edit" onclick="Store.editTask('${currentTask.taskId}')" /></p>
</form>
</div>
<div class="col">
<form id="delete-form-for-${currentTask.taskId}">
<p><input type="submit" class="btn btn-danger btn-sm delete" onclick="Store.deleteTask('${currentTask.taskId}')" value=" x " /></p>
</form>
</div>
</div>
`;
list.appendChild(row);
}
I loop through the tasks in the database to display them using this function: (This could be another place that could've caused this error maybe?)
static displayTasks(){
Store.getTasksArr().then(taskData => {
let tasks = [];
let i;
taskData.map((currentTasks) => {
tasks.push(currentTasks)
})
tasks.forEach((task) => UI.addTaskToTable(task));
})
}
The fetch request:
static editTask = async(id) => {
const res = await fetch('http://localhost:5500/tasks/' + id, {
method: "PATCH",
});
const json = await res.json();
console.log(json);
}
My get, post, and delete all work fine and for the post, I wrote something very similar so I'm also confused as to why the patch doesn't retrieve the data from the textbox but in the post request it does:
router.post('/', ensureAuth, async(req, res) => {
try {
const task = new Task({
task: req.body.newtask,
user: req.user.id
})
const newTask = await task.save()
res.status(201)
res.redirect('/home')
} catch (err) {
res.status(400).json({
message: err.message
})
}
})
I also have a body-parser middleware already defined:
app.use(express.urlencoded({extended: false}));
app.use(express.json());

POST request not working -- sending empty objects [NodeJS]

I am building this school project where we have to create out own API in NodeJs and free-choice frontend. I wrote the following code:
[In public map] app.js
function getAll() {
console.log("Get all")
makeRequest("/poems", "GET")
}
async function getRandomPoem() {
const ids = [1, 2, 3, 4, 5, 6, 7]
const randomId = ids[Math.floor(Math.random() * ids.length)]
const arrayofPoems = await fetch("/poems/" + randomId, {method: "GET"})
const data = await arrayofPoems.json()
const titleBox = document.getElementById("title")
const authorBox = document.getElementById("author")
const textBox = document.getElementById("text")
titleBox.innerText = data.title
authorBox.innerText = data.author
textBox.innerText = data.text
}
function addPoem() {
event.preventDefault();
let title = document.getElementById("titleInput").value
let author = document.getElementById("authorInput").value
let text = document.getElementById("textInput").value
let newPoem = [{
id: 8,
title: "Aaa",
author: "Ccc",
text: "Ddd"
}]
makeRequest("/poems/", "post", newPoem)
}
async function makeRequest(url, reqMethod, body) {
const response = await fetch(url, {
// headers = { "Content-Type": "application/json" },
method: reqMethod,
body:JSON.stringify(body)
})
console.log(response)
const data = await response.json()
console.log(data)
}
[Here the requests to local server] server.js
const express = require('express');
const { poems } = require('./Poems/poemsArray');
const app = express();
const port = 8080;
const allPoems = require('./Poems/poemsArray')
app.use(express.json())
app.use("/", express.static('public'))
app.listen(port, console.log(`App listening on port ${port}`))
// ---------------- POEMS RESOURCE, All endpoints ------------------ //
// Get all
app.get('/poems', (req, res, next) => {
res.json(allPoems)
})
// Get specific
app.get('/poems/:id', (req, res, next) => {
const id = req.params.id
const onePoem = allPoems.find((poem) => poem.id == id)
if(onePoem) {
res.json(onePoem)
} else {
res.status(404).json({ status: "Poem not found! "})
}
})
// Post a poem
app.post('/poems', (req, res, next) => {
allPoems.push(req.body)
res.json({ status: "A new poem has been posted!"})
})
[And last, the HTML with the input fields, where the values should be sent with the POST req] index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Poems from outer space</title>
<script src="app.js"></script>
</head>
<body>
<div id="container">
<div id="poem-container">
<h1 style="color:red;text-align:center;">Poems</h1>
<p style="text-align: center;">Generate a random poem about space!
<button onclick="getRandomPoem()">Generate my poem!</button>
</p>
<div id="showPoem">
<h1 id="title"><!-- Title of poem injected --></h1>
<h2 id="author"><!-- Author of poem injected --></h2>
<p id="text"><!-- Text of poem injected --></p>
</div>
<div id="image-container">
<!-- INJECTED BY EXTERNAL NASA API -->
<!-- EXAMPLE IMAGE TO TEST DELETE WHEN API WORKS -->
<img src="img/apod.jpg" alt="Test Image" width="600px" id="img">
</div>
</div>
<div id="form-container">
<form method="post" action="/poems">
<h1>Send us your poem!</h1>
<label>Your title:</label> <br>
<input type="text" requirede name="title" id="titleInput"> <br>
<label>Your name:</label> <br>
<input type="text" required name="author" id="authorInput"> <br> <br>
<label>Your poem:</label> <br>
<input type="text" required name="text" id="textInput" style="width:500px;height:500px">
<br>
<button type="submit" onclick="addPoem()">Send</button>
</form>
</div>
</div>
</body>
</html>
In the function addPoem() the let newPoem is for testing purposes. The title, author and text should be coming from the form.
Anyone can see what I did wrong?
EDIT: in the makeRequest function the header is commented out, that is because if I leave it in my code, suddenly none of the request work anymore?
Thanks to everybody!
you use headers = which is not valid . try headers : {} .
When you get empty object , try logging the request. It is also possible that the body get sended as a string,which express.json() middleware cannot parse the data. As a result, you get empty object.
async function makeRequest(url, reqMethod, body) {
const response = await fetch(url, {
headers : { "Content-Type": "application/json" },
method: reqMethod,
body:JSON.stringify(body)
})
console.log(response)
const data = await response.json()
console.log(data)
}
If you are trying to access postman after a while it can also cause issue while sending body.
In my case I had done all changes in API, added router,removed validation etc
but at last the culprit was postman as whatever data I was sending, it was showing request.body as {}(empty).
After I re-installed postman it worked!
I just could felt more joyful, it took my 3-4 hours
So you can consider this option as well.

Display a specific user by _id (MongoDB, Node, React) error: Cast to ObjectId failed for value "undefined" at path "_id" for model "user"

I have page where many users are displayed. When I click on a user, it directs to its page. I want that user to be displayed on this page, but I get an error:
Cast to ObjectId failed for value "undefined" at path "_id" for model "user"
Some codes below.
server.js:
app.get('/api/getAnotherUser/:id', getAnotherUser);
getAnotherUser.js:
const User = require('../models/User');
exports.getAnotherUser = async (req, res) => {
try {
const user = await User.findById(req.params.id).select('-password');
res.json(user);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
User schema:
const mongoose = require('mongoose');
const UserSchema = mongoose.Schema({
name: {
type: String,
}
});
module.exports = mongoose.model('user', UserSchema);
Path to this page:
<PrivateRoute path="/user/" component={User} />
This is where I map through all the users, key is _id, and when I click on the button OPEN, it will redirect that specific user's page:
return this.state.users.map((user) => (
<div className="card" key={user._id}>
<div className="image-container">
<img src={user.image} alt="" />
</div>
<div className="card-details">
<p>{user.name}</p>
<p>{user.age}</p>
<p>{user.gender}</p>
<p>{user.location}</p>
</div>
<Link to={`/user/${user._id}`}>
<Button>Open</Button>
</Link>
</div>
));
The page where I want the specific user to be displayed (by _id):
in my browser: http://localhost:3000/user/5ed9d49c7e516616600eb693
in Postman I successfully get the user by _id: http://localhost:5000/api/getAnotherUser/5ed9d49c7e516616600eb693
MongoDB: "_id": "5ed9d49c7e516616600eb693"
export class User extends Component {
constructor(props) {
super(props);
this.state = {
name: ''
};
}
componentDidMount() {
const token = localStorage.getItem('token');
if (token) {
this.setState({ loading: true });
axios.get(`/api/getAnotherUser/${this.props._id}`, { headers: { 'X-Auth-Token': token } }).then((res) =>
this.setState({
name: res.data.name,
})
);
}
}
render() {
return (
<div>
<h1>user: {this.state.name}</h1>
</div>
);
}
}
And this is where I get the error. That specific user by _id is not displayed. What went wrong here? In Postman, with the get request, I can fetch that user by _id, but in my application I must have forgotten something. Could anyone please point it out where? Thanks!
It says this.props._id is undefined, so what you have to do is
componentDidMount() {
const token = localStorage.getItem('token');
console.log(this.props._id) //check if this is undefined
if (token && this.props._id) {
...
}
}
If this.props._id logged as undefined in your browser console then you have to pass the _id from the parent component if it is not undefined in the parent

Categories