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());
Related
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.
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
I am struggling with one issue for really long time. I can't 'transfer' req.params.token and req.params.id to the POST route. What I mean:
router.get('/passwordReset/:token/:id', isNotLogin, (req, res) => {
console.log('token: ' + req.params.token)
console.log('id: ' + req.params.id)
Token.findOne({userId: req.params.id}, function (err, id) {
if (!id) {
res.redirect('/confirmError');
} else {
res.render('view/password/passwordReset');
};
});
});
Results from console.log are (e.g.):
token: 934f569631026e396da5b9a952bfsnx72ba1d2187ecd734b47b3aca89640faf3
id: 60e5560119fcb9627sgar810
Example link:
https://localhost:3000/passwordReset/934f569631026e396da5b9a952bfsnx72ba1d2187ecd734b47b3aca89640faf3/60e5560119fcb9627sgar810
But when it comes to the POST route:
router.post('/passwordReset/:token/:id', isNotLogin, async (req, res) => {
console.log('token: ' + req.params.token)
console.log('id: ' + req.params.id)
await resetPassword(req.params.id, req.params.token, req.body.password);
});
The results of console.log are:
token: :token
id: :id
And that's what I can't solve. I've tried many things, but I just can't come to the right solution.
I've already tried to replace router.post('/passwordReset/:token/:id' to router.post('/passwordReset' but it didn't help.
Form for the password reset:
<form action="/passwordReset/:token/:id" method="POST">
<label for="password">Password</label>
<input type="password" name="password" id="password">
<button type="submit">Zmień hasło</button>
</form>
And the resetPassword function:
async function resetPassword(userId, token, password) {
const passwordResetToken = await Token.findOne({userId: userId});
if (!passwordResetToken) {throw new Error("Invalid password reset token");}
const isValid = await bcrypt.compare(token, passwordResetToken.token);
if (!isValid) throw new Error("Invalid password reset token");
const salt = crypto.randomBytes(32).toString('hex');
const hash = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512').toString('hex');
await User.updateOne(
{_id: userId},
{$set: {hash: hash}},
{$set: {salt: salt}},
{new: true}
);
const user = await User.findById({_id: userId});
sendEmail(user.username, "Password Reset Successfully", {email}, "./email/template/resetPassword.handlebars");
await passwordResetToken.deleteOne();
return true;
};
The error is:
(node:1811) UnhandledPromiseRejectionWarning: CastError: Cast to ObjectId failed for value ":id" (type string) at path "userId" for model "Token"
Whole code: https://github.com/kbilak/ToDoApp
Send the actual value for token and id to the html page. So your form action should be something like this when you inspect the code on your browser
/passwordReset/934f569631026e396da5b9a952bfsnx72ba1d2187ecd734b47b3aca89640faf3/60e5560119fcb9627sgar810
Using the ejs template format
<form action="/passwordReset/<%= token %>/<%= id %>" method="POST">
<label for="password">Password</label>
<input type="password" name="password" id="password">
<button type="submit">Zmień hasło</button>
</form>
In order to send the value to your html page, you need to add those value when you are rendering the page. Just like you have it here
router.get('/passwordReset/:token/:id', isNotLogin, (req, res) => {
token = req.params.token
id =req.params.id
Token.findOne({userId: req.params.id}, function (err, id) {
if (!id) {
res.redirect('/confirmError');
} else {
res.render('view/password/passwordReset', {
token: token
id: id
} );
};
});
});
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.
I have a ReactJS form, in which you can enter a new username, which is sent to the server via axios POST and finally saved to a database.
When I click "add" (and thus submit the form), however, it does not save the username I typed into the form input, but returns an empty string.
This is the code for the form including the submit-function:
addUser (e) {
var data = {
entry: this.state.currentname
};
axios.post('users/newuser',
data, {
headers: {"Content-Type": "application/x-www-form-urlencoded"}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
<div className="App">
<form onSubmit={this.addUser} encType="multipart/form-data">
<label>New username:</label>
<input name="username" value={this.state.currentname} onChange={this.handleChange}></input>
<button type="submit" >Add</button>
</form>
<h1>Current Users: </h1>
<ul>
{this.state.users.map((name, n) =>
<li key={"user_"+n}>{name.username}</li>
)}
</ul>
</div>
);
}
users is the file which contains my curd functions. Here is how I'm currently adding data to the database:
router.route('/newuser')
.post(function (req,res) {
var newUser = new Todo();
newUser.username = req.body.entry;
newUser.save(function (err) {
if(err)
res.send(err);
res.send('User added successfully!');
});
})
Unfortunately, I'm not exactly sure what is going wrong, but I assume that I'm not requesting the username correctly in users.js, since it does insert new data into my database, without a username though.
This is my folder structure (don't know if it's relevant):
-client
public
-index.html
src
-App.js(contains the form)
-server.js
-routes
-users.js
What am I doing wrong exactly and how can I fix that?
So I'm not sure if this is the best way to solve it or if there are better ways (I won't mark my own answer as correct answer since I don't know how correct it is).
What I did was the following:
I added a query parameter to the string in my axios.post request and called for that parameter value in node.js in the function where I insert new data.
These are the parts, which I changed:
App.js:
axios.post('users/newuser?username='+data.entry,
data, {
headers: {"Content-Type": "application/x-www-form-urlencoded"}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
user.js:
router.route('/newuser')
.post(function (req,res) {
var newUser = new Todo();
newUser.username = req.body.username;
newUser.save(function (err) {
if(err)
res.send(err);
res.send('User added successfully!');
});
})