Redirect response into a dynamic url - node.js

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

Related

i am getting an error while i am updating my data using express node.js

i am trying to update my category name but when i click on the submit button it gives me an error however i tested my code using postman and it actually updated but in that case i have to pass the product's id manually.
the callback function of the update category:
exports.updateCategory = (req, res, next) => {
console.log('GET update CATEGORY /update-category');
if (!req.body) {
return res
.status(400)
.send({ message: "Data to update can not be empty" })
}
const id = req.params.id; //req.body.id >> body means in the html/ejs file the name should be id
categorySchema.findByIdAndUpdate(id, req.body, { userFindAndModify: false })
.then(data => {
if (!data) {
res.status(404).send({ message: `Cannot Update user with ${id}` })
}
//updating the data
else {
res.render('category/edit_category.ejs', {
category: data //variable that i am going to use is category in the ejs
})
}
})
.catch(err => {
res.status(500).send({ message: "Error update user information" })
})
}
the update form >>
<form action='<%=`/halalMunchies/update-category/${category._id}`%>' method="post">
.
.
.
<label class="control-label" for="categoryName"> Category Name </label>
<input type="hidden" name="id" value="" id="id">
<input id="categoryName" class="form-control" value="<%= category.categoryName%>" name="categoryName" required autofocus="autofocus" />
the router.js
//PUT UPDATE CATEGORY
router.put('/update-category/:id', categoriesController.updateCategory);
router.get('/update-category/:id', categoriesController.updateCategory);
the error message on page is :
Cannot POST /halalMunchies/update-category/618632e3a5ad00fa5368ad5c
As the error message tells you, you did not provide a post endpoint in your router. Since you're using the post method in your form, you need to add the following in your router.js:
router.post('/update-category/:id', categoriesController.updateCategory);

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

How to display express errors in ejs

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>
<% } %>
...

Failing to make axios.put from react to node.js server

I have react component where i trying to change status of object. However I'm failing and didn't understand whether I'm on right way.
This is nodeJS server with express and mongoose.
Frond end side:
handleSubmit2(event){
const body= {status: this.state.newstatus}
event.preventDefault()
axios.put('http://127.0.0.1:3010/api/users/:user_id',{body})
alert("Status Changed")
}
in render:
{hits.map(item => <li key={item._id}> {item.firstname} {item.lastname} <br /> <strong> {item.status}</strong>
<form autoComplete="off" className="new_status" onSubmit={this.handleSubmit2}>
<FormControl>{this.loadoptions()}</FormControl>
<input type="submit" value="Submit" className="input"/>
<br />
</form>
My back-end:
router.put( '/:user_id', ( req, res ) => {
console.log(req.body.body.status)
User.findByIdAndUpdate(req.params.user_id, req.body.body.status)
.then(data => {
if(follower)
res.send(data);
else
res.status(404).send("Follower not found");
})
.catch (e => res.status(400).send("Follower not exist"));
});
UserSchema:
const userSchema = new mongoose.Schema ({
firstname: String,
lastname: String,
status: String
});
const User = mongoose.model('User', userSchema);
Link to git:
https://github.com/fmpro12/users_add
When i submit form i want to change status of specific people from array.
What I'm doing wrong?

Node.js (Express) Form Clears on Submission

I am working on a really basic registration form in Node.js (with Express), and I am trying to find the easiest way to provide basic form validation. I've gone with "Express-Validator", which seems to do a fine job. However, my goal is to simply show any validation messages that are required and to leave the values entered by the user alone.
It seems that the request information is not making it back into the res.render, which I guess makes sense. However, I've looked everywhere I can think of and I can't find any reference that discusses how to keep form fields populated after showing error messages.
Below is a small snippet describing my approach:
post: function(req, res){
var userName = req.body.username;
var password = req.body.password;
//Validate input
req.assert("username", 'Invalid email address.').isEmail();
req.assert("password", 'Password cannot be empty.').notEmpty();
req.assert("passwordConfirm", 'Passwords entered do not match!').equals(password);
//Make sure we have no validation errors
var pageErrors = req.validationErrors();
if(!pageErrors)
{
userModel.CreateUser(userName, password, function(err){
if(err)
{
//there was a problem inserting new user... probably already exists
//will need to check the error to confirm
var dbErrorMessage = "Could not insert record into database!";
if(err.code === 11000)
{
//this is a duplicate entry
dbErrorMessage = "A user with that email address already exists!";
}
res.render('register.html', { pageErrors: [{msg: dbErrorMessage }]});
}
else
{
res.render('register.html', { successMessage: successMessage });
}
});
}
else
{
res.render('register.html', { pageErrors: pageErrors });
}
Unfortunately, you have to repopulate the form manually. If you get any page errors, you will pass back the form values to the view.
if(!pageErrors)
{
// ...
}
else
{
res.render('register.html', {
pageErrors: pageErrors,
userName: userName
});
}
And in your view, you would do a simple check to see if their are any errors and repopulate accordingly. You would have to keep track of what errors are produced for each form field.
<% if (userNameError) { %>
<input type="text" name="userName" value="<%- userName %>" />
<% } else { %>
<input type="text" name="userName" />
<% } %>
Another popular way is to send your form via ajax to to the server, and do all your validations. If there is an error, the entered form data remains and you would show the error, otherwise redirect after the successful login. Below is an example of how to submit a form with javascript.
$("#login-button").live("submit", function (e) {
// this will prevent the form from being uploaded to the server the conventioanl way
e.preventDefault();
// the form data
var data = $(this).serialize();
// this logs the user in
$.ajax({
type: 'POST',
url: BASE_URL + '/login',
data: data,
dataType: 'json',
success: function (data, status) {
// successful
},
});
// superfluous fallback
return false;
});
There is an easy way is you are using
app.use(express.bodyParser()) and app.use(expressValidator());
You can use req.body
res.render('register.html', {
pageErrors: pageErrors,
validated: req.body
});
And I'm not sure which templating language you are using but you could do something like..
<input type="text" name="userName" value="<%= pageErrors.userName.value || validated.userName %>" />
This then gives back the good input if ok or the bad input if it needs correcting.
You can get this done using connect-flash
Below are the code snippets in different files in order to get the values entered by the user back in form when validations fail while doing signup with passport.
Run this below command to add new package into package.json
npm install connect-flash --save
app.js
var flash = require('connect-flash');
app.use(flash()); // add this above passport initialize
app.use(passport.initialize());
app.use(passport.session());
config/passport.js (Please focus on form data loading into flash)
passport.use('local.signup', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, function (req, email, password, done) {
req.checkBody('first_name', 'Firstname is missing').notEmpty();
req.checkBody('last_name', 'Lastname is missing').notEmpty();
req.checkBody('email', 'Invalid email').notEmpty().isEmail();
req.checkBody('password', 'Password is too short. Minimum size is 6.').notEmpty().isLength({min:6});
req.checkBody('confirm_password', 'Password and confirm password didn\'t not match').equals(req.body.password);
var errors = req.validationErrors();
if (errors) {
var messages = [];
errors.forEach(function(error) {
messages.push(error.msg);
});
req.flash('formdata', req.body); // load form data into flash
return done(null, false, req.flash('error', messages));
}
User.findOne({'email': email}, function (err, user) {
if (err) {
req.flash('formdata', req.body); // load form data into flash
return done(err);
}
if (user) {
req.flash('formdata', req.body); // load form data into flash
return done(null, false, {message: 'Email is already in use.'});
}
var newUser = new User();
newUser.first_name = req.body.first_name;
newUser.last_name = req.body.last_name;
newUser.email = email;
newUser.password = newUser.encryptPassword(password);
newUser.save(function(err, result) {
if (err) {
return done(err);
}
return done(null, newUser);
});
});
}));
routes/index.js (Please focus on form data in flash loaded back into a variable)
router.get('/signup', function (req, res, next) {
var messages = req.flash('error');
var formdata = req.flash('formdata'); // Get formdata back into a variable
res.render('user/signup', {csrfToken: req.csrfToken(),
messages: messages, // pass it here to access in view file
hasErrors: messages.length > 0,
formData: formdata[0]
});
});
router.post('/signup', passport.authenticate('local.signup', {
badRequestMessage: 'Please fill the form with all details',
failureRedirect: '/user/signup',
failureFlash: true
}), function (req, res, next) {
if (req.session.oldUrl) {
var oldUrl = req.session.oldUrl;
req.session.oldUrl = null;
res.redirect(oldUrl);
} else {
res.redirect('/user/profile');
}
});
views/signup.hbs (Please focus on values in input elements)
<form class="wow fadeInUp animated" data-wow-delay=".7s" action="/user/signup" method="post" >
<input type="text" placeholder="First Name" name="first_name" value="{{ formData.first_name }}">
<input type="text" placeholder="Last Name" name="last_name" value="{{ formData.last_name }}">
<input type="text" class="email" placeholder="Email Address" name="email" value="{{ formData.email }}">
<input type="password" name="password" value="" class="lock" placeholder="Password">
<input type="password" name="confirm_password" value="" class="lock" placeholder="Confirm Password">
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
<input type="submit" name="Register" value="Register"></form>
Hope this helps.
set a variable for all the input, for example
var inputData = {
firstname : req.body.firstname,
lastname : req.body.lastname,
email : req.body.email,
username : req.body.username,
password : req.body.password,
password_confirmation : req.body.password_confirmation,
agreetoterms: req.body.agreetoterms
}
and then pass that variable to the view
res.render('register.html', { pageErrors: [{msg: dbErrorMessage }], inputData: inputData });
then in your view
value="<%= inputData.userName %>"
if you are using jade and form Validator from npm , the best part is that you can an if statement in jade and then you simply check if error then with res.render we send the objects also . See This
if(errors){
res.render('register',{
errors : errors,
name : name,
email : email,
username : username,
password : password,
password2 : password2
});
And in jade you do this
input.form-control(name='name',type='text',placeholder='Enter Name',value = (errors ? '#{name}':''))
so if there is errors value will set to variable in name which will rendered when we send back
I think you can also be done in Angular2/Angular.js
Well, there is a simple way to achieve this. Do following in controller:
else{
res.render('register.html', { pageErrors: pageErrors, inputData: req.body});
}
Now, in your ejs (or do it according to your view engine) file do following:
<input type="text" name="name" value="<% if(typeof pageErrors !== "undefined"){ %> <%- inputData.name %> <% } %>" class="form-control">
check http://www.quietless.com/kitchen/building-a-login-system-in-node-js-and-mongodb/
on register.html make this
var data = {};
data.user = $('#user-input').val();
data.email = $('#email-input').val();
data.pass = $('#pass-input').val();
$.ajax({ url: '/signup'
, type: 'POST'
, data: JSON.stringify(data)
, contentType: 'application/json'
, dataType: 'html'
})
.done(function(data) {
if (data == 'ok') {
$('#content').html('You are registered!');
}
else $('#account-form-container').append('<br>error:' + data);
});
there might be error like: CANNOT POST /
in this case the author of tutorial on link above uses lib $.ajaxForm
you can also use https://github.com/felixge/node-formidable
or $('#myform').submit() replace to $('#submit-a-link').click()

Resources