How to display express errors in ejs - node.js

I am validating emails users enter using "emailCheck" and a piece of code I found on another question, this is the code in my app:
app.post("/blog", (req, res) => {
const name = req.body.name;
const email = req.body.email;
emailCheck(email).then(() => {
const newSubscriber = {name: name, email: email};
Subscriber.create(newSubscriber).then(() => {
res.redirect("/blog")
})
.catch((error) => {
res.json({serverErrorEmailExistence: "This email adress is already in use!"})
})
})
.catch(() => {
res.json({serverErrorEmailExistence: "This Email doesn't exist!"})
})
})
This works as it is, but the errors are shown on a new blank page. I would like to show the error under the form that I have. Form is in included as a partial in my app.
Here is the form html:
<section id="emailSub">
<div id="emailContainer">
<h1>Subscribe to my Newsletter</h1>
<p>You will get weekly emails when a post is published.</p>
<form action="blog" method="POST" id="emailForm" autocomplete="off">
<div class="field">
<input type="text" placeholder="Name: " name="name" required>
</div>
<div class="field">
<input type="email" placeholder="Email: " name="email" required>
</div>
<button type="submit">Subscribe!</button>
</form>
</div>
<div id="thankYouMsg">
<h1>Thank you for subscribing!</h1>
<p><i class="far fa-check-circle"></i></p>
</div>
<button id="exitForm"><i class="fas fa-times"></i></button>
</section>
I include this on the blog main page with:
<%-include("partials/subscribe") %>
And here is my subscriber model:
const mongoose = require("mongoose");
const SubscriberSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
}
});
module.exports = mongoose.model("Subscriber", SubscriberSchema)
How can I show that error in the form?
The div with the ID thankYouMSg is shown after a successful form submit, usually it is hidden with Css.
I tried searching for this and I found a lot of answers but I either don't know how to include them in my code or I don't understand enough to search for the right answer (might be both). To be honest, I just included the emailcheck code in my app the best I know how. I don't really understand what .catch(error) is delivering.
Thank you
Following the answer I tried:
.catch(() => {
res.render("/blog", {errorMessage: "This email adress is already in use!"});
})
})
.catch(() => {
res.render("/blog", {errorMessage: "This Email doesn't exist!"})
})
But, I get the "cannot look up view /blog in views". I tried the same with
res.redirect and it just loads without anything happening.

What's happening is that in case of an error, you catch this error and return a json-response which the browser cannot render directly in html.
What you can do instead, is re-send your subscribe page and pass the caught error message to that page, which you can render there. Something like this should help you get started:
in your app.js
...
.catch(() => {
res.render("your-subscribe-template.ejs", {
errorMessage: 'This Email doesn\'t exist!'
});
});
...
in your template.ejs:
...
<% if (typeof errorMessage !== "undefined") { %>
<p>Form could not be submitted due to the following error:</p>
<p><%= errorMessage %></p>
<% } %>
...

Related

Update data in mongodb with input valors

I have three input to obtain three different values. Im using express.js , node.js, mongodb and ejs templates.
<form action="/save-profile/<%= user.id %>/<%= user.name %>/<%= user.lastname %>/<%= user.description %>" method="POST">
<div class="input-group mb-3">
<span class="input-group-text" id="basic-addon1">Name</span><%= user.username %>
<input type="text" class="form-control" placeholder="'John'" aria-label="Username" name="username">
<span class="input-group-text">lastName</span><%= user.lastname %>
<input type="text" class="form-control" placeholder="" aria-label="Server" name="lastname">
</div>
<div class="input-group">
<span class="input-group-text">Description:</span>
<textarea class="form-control" aria-label="With textarea" placeholder="" name="description"><%= user.description %></textarea>
</div>
</p><br>
<button class="btn btn-primary mb-10 btn-lg">Save</button>
</div>
</div>
In js file:
router.post('/save-profile', async (req, res) => {
const profile_id = await User.findById({ _id: req.body.id })
const updatedName = await User.findOneAndUpdate({ username: req.body.username})
const updatedlastname = await User.findOneAndUpdate({ apellido: req.body.lastname })
const updatedDescription = await User.findOneAndUpdate({ description: req.body.description })
console.log(profile_id,updatedName,updatedApellido,updatedDescription)
res.redirect('/profile')})
I tried to do it with a get but it didn't work
Firstly, action attribute in the form tag accepts the path where you are handling the form data. You only need to pass the user.id, there's no need to pass the other fields for this use-case.
<form action="/save-profile/<%= user.id %>" method="POST">
...
</form>
Secondly, in your route handler, the database record can be updated using only a single findOneAndUpdate call. You don't need to call it multiple times for every field if you're only going to update a single record.
The path written in action attribute will appear as /save-profile/1, for instance, in your route handler. Value preceding /save-profile/ i.e. 1 can be accessed by modifying the path in route handler as /save-profile/:id and in the callback you can get it by req.params.id
Finally you have this.
router.post('/save-profile/:id', async (req, res) => {
const response = await User.findOneAndUpdate(
{ _id: req.params.id },
{
username: req.body.username,
apellido: req.body.lastname,
description: req.body.description
},
{ new: true }
)
// Do something with response
res.redirect('/profile')
})

Trouble rendering of documents with referenced fields on Vue.js mongoose

Recently, I picked up Vue.js and Mongoose to develop a personal project to mainly track Ingredients' on hand quantities on a certain online game.
Different dishes require different ingredients. Lotus Seed - Bird Egg soup, Jewelry Soup and Jade Parcels all require different number of Lotus Head as its ingredients.
I update an ingredient's quantity by using updateOne on ingredients collection.
Unfortunately, I originally embedded the ingredients on foods / dishes, which I realized problematic
recently, coz literally you just count ingredients what you currently have.
So a food document now looks like this
{
"_id" : ObjectId("5fca4ada32195d5814510242"),
"foodName" : "Lotus Seed and Bird Egg Soup",
"onHandQty" : 20,
"ingredients" : [
"5fca481432195d581451023f",
"5fca483932195d5814510240",
"5fca48a232195d5814510241"
]
}
I read about Mongoose's populate(), and tested to output one food/dish. Unfortunately there's nothing coming out of Vue.js front-end after trying that code.
server/models/Food.js
const { Router } = require('express');
const FoodItem = require('../../models/Food');
const IngredientItem = require('../../models/Ingredient');
const router = Router()
router.get('/', async(req, res) =>{
try {
const food = await FoodItem.findOne({
foodName: 'Lotus Seed and Bird Egg Soup'
}).populate('ingredients').
exec(function (err, food) {
if (err) return handleError(err);
console.log('The food is %s', food.foodName);
});
res.send(food);
} catch (error) {
res.status(500).json({
message: error.message
})
}
});
module.exports = router
A portion of component where ingredients are rendered
client/src/components/Food.vue
<div class="tile is-ancestor">
<div class="tile">
<div class="tile is-parent">
<div class="tile is-child box">
<template v-if="food.ingredients">
<div class="ingredients-block">
<p>Ingredients List:</p>
<ul class="ingredients-list">
<li class="row" v-for="ingredient in food.ingredients" :key="ingredient._id">
<div id="ingredient-image-container">
<img class="image is-64x64" :src="require(`../assets/images/food_inv/${ingredient.imagePath}.png`)" alt="ingredient.ingredientName" :title="ingredient._id">
{{ingredient.ingredientName}}
</div>
<div class="required-qty-container">
<!-- <i class="material-icons" id="required-inner-qty">food_bank</i> -->
Required:
{{ ingredient.requiredQty }}
</div>
<div class="on-hand-qty-container">
<p>On Hand:</p>
<input v-if="ingredient.onHandQty < ingredient.requiredQty" class="input is-danger on-hand-input" type="number" v-model="ingredient.onHandQty" min="0">
<input v-else class="input is-primary on-hand-input" type="number" v-model="ingredient.onHandQty" min="0">
<!-- <button class="button is-primary save-button" #click="test({ingredient_id: ingredient._id, onhandqty: ingredient.onHandQty})"><i class="material-icons">save</i></button> -->
<button class="button is-primary save-button" #click="$emit('update-qtys', {ingredient_id: ingredient._id, onhandqty: ingredient.onHandQty})"><i class="material-icons">save</i></button>
</div>
</li>
</ul>
</div>
</template>
</div>
</div>
</div>
</div>
Whole project on Github: Food Inventory
Quick Fixes,
change your food schema's ingredients field from object to array,
const foodSchema = new mongoose.Schema(
{
foodName: String,
imagePath: String,
effect: String,
onHandQty: Number,
// correct this to array
ingredients: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Ingredient'
}]
}
);
there are 2 ways to call mongoose methods first exec() with callback and second without exec() callback,
exec with callback that you have used buy need to send response (res.send(food) or res.json(food)) from inside the exec call function,
router.get('/', async(req, res) =>{
try {
await FoodItem.find()
.populate('ingredients')
.exec(function (err, food) {
if (err) return handleError(err);
console.log('The food is %s', food);
// put response here
res.json(food);
});
} catch (error) {
res.status(500).json({ message: error.message })
}
});
exec without call back
router.get('/', async(req, res) =>{
try {
const food = await FoodItem.find()
.populate('ingredients')
.exec();
res.json(food);
} catch (error) {
res.status(500).json({ message: error.message })
}
});

Redirect response into a dynamic url

I am using node js with:
express framework
hbs framework
mongodb
My task is to redirect my response to a url with a format /users/:name/course/:courseId.
After running the code with the parameters name=James and courseId=1234, I get /users/James/course/1234 in the browser url. However, I am not able to post to /users/James/course/1234.
Edit: the data is being successfully posted, but I am getting a message Cannot GET /users/James/course/1234.
I have the following code:
app.js:
app.get('/', (req, res) => {
res.render('form.hbs')
})
app.post('/saveUsers', [
// firstname must contain letters only
check('firstname', 'First name should contain only letters').isAlpha(),
// email must be in a email format
check('email', 'Email field must be in a email format').isEmail(),
// courseId must be numerical and exactly 4 digits
check('courseId', 'course ID should contain only numbers').isNumeric(),
check('courseId', 'course ID should be exactly 4 digits').isLength({ min: 4, max: 4 })
], (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array(), data:req.body})
}
var db = utils.getDb()
db.collection('users').insertOne({
name: req.body.firstname,
email: req.body.email,
courseId: req.body.courseId
}, (err, result) => {
if (err) {
Response.send('Unable to insert a student')
}
console.log(result.ops)
nameUrl = result.ops[0].name
courseIdUrl = result.ops[0].courseId
res.redirect(`/users/${nameUrl}/course/${courseIdUrl}`)
})
})
app.post(`/users/${nameUrl}/course/${courseIdUrl}`, (req, res) => {
res.json(result.ops)
})
form.hbs:
<!DOCTYPE html>
<html>
<body>
<h1>Welcome</h1>
<p>Enter your name, email and course ID:</p>
<form action="/saveUsers" method="POST" id="myForm">
<input type="text" placeholder="firstname" name="firstname">
<input type="email" placeholder="email" name="email">
<input type="text" placeholder="courseId" name="courseId">
<input type="submit" value="Submit">
</form>
</body>
</html>
It looks like you are using template literal string interpolation where you should be using the Express syntax for route parameters. See the "route parameters" section in the docs.
Instead of:
app.post(`/users/${nameUrl}/course/${courseIdUrl}`, (req, res) => {
res.json(result.ops)
})
Try:
app.post(`/users/:name/course/:courseId`, (req, res) => {
res.json(result.ops)
})

Editing text on a page in node js

I am trying to implement a simple edit feature in my app. In my profile.handlebars file, I have an edit button. When clicked, I'd like the user's information to appear in the text input fields on the form in order to allow the user to edit their existing information.
Right now, they would have to input all of their information over again (and every field in the form would need to be filled out due to validation that I have implemented), click Submit, and their profile can be updated. Is this possible without using a framework (like Angular)? For example, in LinkedIn, a user can hover over a section of their profile causing the edit buttons to highlight, then click a single edit button, and they're instantly in editing mode. That might be too advanced for my purposes right now, but eventually, I'd love to have functionality like that.
I have a post request in my routes file to handle a user posting information to their profile:
router.post('/add', function(req, res) {
req.checkBody({
'city': {
errorMessage: 'Please enter your city'
},
'state': {
errorMessage: 'Please enter your state',
notEmpty: true
},
'zip': {
errorMessage: 'Please enter your zip code',
notEmpty: true
},
'about': {
errorMessage: 'Please briefly describe yourself',
notEmpty: true
}
});
console.log("req.user " + req.user);
var errors = req.validationErrors();
if (errors) {
res.render('profile', {
errors: errors
});
} else {
var user_info = new User_Info({
city: req.body.city,
state: req.body.state,
zip: req.body.zip,
about: req.body.about,
user_name: req.user.username
});
user_info.save(function(err, user_info) {
if (err) throw err;
});
res.redirect('profile/' + req.user.username)
}
})
Then, I have my profile.handlebars file:
{{#if errors}}
Uh oh! Something went wrong. Please review the below errors, and try again.<br><br>
<ul>
{{# each errors }}
<li style="color: red">{{this.msg}}</li>
{{/each}}
</ul>
{{else}}
<h3 align="center">Profile ({{user_name.name}})</h3>
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="thumbnail" style="border-radius: 12px">
<div class="caption">
<p>City: {{# each information }} {{this.city}} {{/each}}</p>
<p>State: {{# each information }} {{this.state}} {{/each}}</p>
<p>Zip: {{# each information }} {{this.zip}} {{/each}}</p>
<p>About: {{# each information }} {{this.about}} {{/each}}</p>
<div class="btn-group">
<button type="Edit" class="btn btn-danger dropdown-toggle deleteLocation" data-id="{{this.id}}">Edit</button>
</div>
</div>
</div>
</div>
</div>
<br>
<center>
<form method="POST" action="/users/add">
<input type="text" name="city" placeholder="City" style="text-align: left">
<br><br>
<input type="text" name="state" placeholder="State" style="text-align: left">
<br><br>
<input type="text" name="zip" placeholder="Zip" style="text-align: left">
<br><br>
<textarea name="about" placeholder="About You" style="text-align: left; resize: both;" rows="5" cols="50"></textarea>
<br><br>
<div class="btn-group">
<button type="submit" class="btn btn-success dropdown-toggle" aria-haspopup="true" aria-expanded="false">Submit</button>
</div>
<br><br>
</form>
</center>
{{/if}}
Please let me know if you need additional info to help me solve this issue. Thanks!
you can use this code for node for editing the parameters , city,state,zip and about.
router.post('/add', function (req, res) {
var users = req.Collection;
var city = req.body.city;
var state = req.body.state;
var zip = req.body.zip;
var about = req.body.about;
var user_id = req.body.user_id;
if (city && state && ) {
users.findOneAndUpdate({_id: user_id}, {$set: {city: city, state: state, zip: zip, about:about}}, function (err, user) {
if (err) {
res.json({status: 0, message: err});
}
if (!user) {
res.json({status: 0, msg: "not found"});
} else {
res.json({status: 1, city: city, state: state, zip: zip, about:about, message: " edit success"});
}
})
} else {
res.json({status: 0, msg: "Invalid Fields"});
}
});

express-validator retain input values

Im new no NodeJS, ExpressJS and Jade. I'm using express-validator for validating a form.
In route:
req.assert('firstName', 'Name is required').notEmpty();
req.assert('lastName', 'Name is required').notEmpty();
req.assert('email', 'A valid email is required').isEmail();
var errors = req.validationErrors();
....
if (errors){
res.render('users/new', {
errors: errors
});
}
....
In Jade, to display error:
- if (errors)
div.alert.alert-error
ul
- each error in errors
li= error.msg
Above code works. I want the input values to be retain in the form so that user's don't to type it again.
input#name(type="text", name="name", value="?")
How?
Thanks
You can simply put back values to rendered view and interpolate it into input tag.
if (errors) {
res.render('users/new', {
errors: errors,
firstName: firstName,
...
});
}
and in jade template put
input#name(type="text", name="firstName", value="#{firstName}")
or
input#name(type="text", name="firstName", value= firstName)
it depends on your personal style.
It is better to define values like person.first, person.last and person.email, because you can put only person into your rendered view.
Your users/new.js file
var body = req.body;
var form = {
name: body.name,
surname: body.surname,
...
};
req.checkBody('name', 'Name is required').notEmpty();
req.checkBody('surname', 'Surname is required').notEmpty();
var errors = req.validationErrors();
if (errors) {
//Pass both errors and form objects as local variables
res.render('users/new', { errors: errors, form: form });
} else {
....//write to DataBase
}
new.handlebars file
{{#if errors}}
{{#each errors}}
<div class="alert alert-danger">{{msg}}</div>
{{/each}}
{{/if}}
<form class="" method="post" action="/users/new">
<p>Name*</p>
<input type="text" value="{{form.name}}" name="name" />
<p>Surname *</p>
<input type="text" value="{{form.surname}}"name="surname" />
</form>

Resources