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 })
}
});
Related
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')
})
I'm a beginner web developer (currently in the learning phase).
So, I was trying to make my first minor backend project (a todo app) and ran into a problem with the database.
I'm using MongoDB and Mongoose as database setup and express with NodeJS as backend (with ejs as template engine).
The problem is I'm not able to post stuff to the database. It is giving error when I'm submitting the form (POST method).
And the after setting up a connection, It is not showing on Robo 3T.
I'm not sure where I'm going wrong. Please help me out with this.
The used files are attached below.
Directory structure
index.js
/** #format */
//requiring express
const express = require("express");
//setting up the port
const port = 1111;
//this module provides a way to work with directories and file paths
const path = require("path");
//requiring configuration for setting up the database to be accessed by mongoose
const db = require("./config/mongoose");
//requiring Task schema/model
//using this we will create entries and populate our collection
const Task = require("./models/task");
const { create } = require("./models/task");
//firing up express
const app = express();
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views"));
app.use(express.urlencoded({ extended: true }));
app.use(express.static(__dirname + "/assets"));
let task_list = [
{
title: "College",
due_date: "2012-12-13",
category: "College"
},
{
title: "Home",
due_date: "2012-12-13",
category: "Home"
},
{
title: "Work",
due_date: "2012-12-13",
category: "Work"
},
{
title: "Group",
due_date: "2012-12-13",
category: "Group"
},
];
app.get("/", function (req, res) {
Task.find({}, function (err, task) {
if (err) {
console.log("Error occured!");
return;
}
return res.render("home", {
tasks: task
});
});
});
app.post("/new-task", function (req, res) {
console.log(req.body);
Task.create(
{
title: req.body.title,
due_date: req.body.due_date,
category: req.body.category
},
function (err, newt) {
if (err) {
console.log("Error while posting");
return;
}
console.log("Newtask created!: ", newt);
return res.redirect("back");
}
);
});
//creating a listener to the specified port
app.listen(port, function (err) {
if (err) {
console.log(`Some error occured at port: ${port}
Please try again later`);
return;
}
console.log("Yay! Server is running at # port:", port);
});
Note: I was using task_list array to check if post is working or not and it was working. But problem is occurring when I'm trying it with a persistent database (MongoDB).
mongoose.js (For connection with database)
//requiring mongoose to set up connection with database
const mongoose = require('mongoose');
//setting up connection
mongoose.connect('mongodb://localhost:27017/tasks_db', {useNewUrlParser: true, useUnifiedTopology: true});
//to check if the connection is successful or some error occured
const db = mongoose.connection;
db.on('error', console.error.bind(console, "!! Error setting up connection with database !!"));
db.once('open', function() {
console.log("Connection with database is successful!");
});
task.js (Containing the schema)
//requiring mongoose
const mongoose = require('mongoose');
//creating the schema for the document of collection
const taskSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
due_data: {
type: Date,
required: true,
get: value => value.toDateString()
},
category: {
type: String,
}
});
//compiling our schema into a model (a class for interacting with MongoDB) (an instance of model is called a document)
const Task = mongoose.model('Task', taskSchema);
module.exports = Task;
home.ejs (containing the view)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Your TO-DO List</title>
<script src="https://kit.fontawesome.com/fcba9de078.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="/css/style.css" />
</head>
<body class="toggler">
<!-- The main box containing the list and the form to add or remove item -->
<div id="todo-box-div" class="flex-row sizi">
<!-- div containing form for adding items to todo-list -->
<div id="todo-form-div" class="flex-column sizi">
<!-- div header -->
<h1>Do It!</h1>
<!-- form for adding items -->
<form action="/new-task" method="POST" id="to-do-form" class="flex-column up-down">
<input id="title" name="title" class="bottom-b font-4" type="text" placeholder="I have to do this..." required />
<!-- <textarea id="description" class="bottom-b font-4" type="text"></textarea> -->
<!-- date and category will be inline for bigger widths -->
<div id="input-inline" class="flex-row">
<input id="date" name="due_date" min="2020-07-20" class="bottom-b font-2" type="date" required />
<select id="category" name="category" class="bottom-b font-2 dropdown" type="text">
<option value="default" selected disabled>Choose a category</option>
<option value="Work">Work</option>
<option value="College">College</option>
<option value="Home">Home</option>
<option value="Group">Group</option>
</select>
</div>
<!-- button for adding item -->
<button type="submit" class="font-4" id="add"><i class="fas fa-plus"></i>Add to list</button>
</form>
</div>
<!-- div containing list items (scrollable) and button to delete items -->
<div id="todo-list-div" class="flex-column sizi">
<!-- button for deleting items -->
<button type="submit" class="font-4" id="delete"><i class="far fa-trash-alt"></i>Done</button>
<!-- list containing todo items -->
<ul>
<% for(let a of tasks) { %>
<li>
<div class="list-item">
<input type="checkbox" class="pointer" />
<div class="name-date font-3">
<p><%= a.title %></p>
<p><i class="far fa-calendar-alt"></i><%= a.due_date %></p>
</div>
<div id="redundant"></div>
<div class="categ-button font-2 disp"><%= a.category %></div>
</div>
</li>
<% } %>
</ul>
</div>
</div>
<script src="/js/script.js"></script>
</body>
</html>
Note: The form is in the todo-form-div under todo-box-div.
Example error:
Here, if you see it is printing "Error while posting" on submitting the form. The req.body is getting printed. This error message is in the error handling of app.post() method in index.js.
I was stuck for too long and decided to post it here and get some guidance. Please help me out with this.
Try the following in post method instead of Task.create() and let me know if it works
const task = new Task({title: "Work",
due_date: "2012-12-13",
category: "Work"})
task.save();
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>
<% } %>
...
I have tried almost all the solutions but couldn't get this fixed.
I'm trying to get multiple collection of data (Mongodb Atlas) onto one ejs file. But I can't get data onto the rendered ejs sheet. But when I tested with locus I can see all the data received from the database up to res.render("/vendor/show"). But it is not passing onto ejs template.
I have three sets of data sitting on the mongo Atlas:
const vendorSchema = new mongoose.Schema({
name:String,
image:String,
imageId:String,
});
const newpromoSchema = new mongoose.Schema({
name:String,
image:String,
imageId:String,
vendor:String,
description:String,
sdate:String,
edate:String,
});
const neweventSchema = new mongoose.Schema({
name:String,
image:String,
imageId:String,
description:String,
vendor:String,
sdate:String,
edate:String,
});
router.get("/vendors/:id",function(req,res) {
var events={}; //Create Empty event Object
var promotions={}; //Create Empty promotion Object
var vendors={};//Create Empty vendor Objext
Promotion.find({},function (err,allPromotions) {
if (err) {
console.log(err);
} else {
//Find Collection And Assign It To Object
promotions=allPromotions;
}
});
Event.find({},function(err, allEvents) {
if (err) {
console.log(err);
} else {
events=allEvents;
}
});
Vendor.find({},function(err,allVendors){
if(err){
console.log(err);
}else{
vendors=allVendors;
//find order collection and passing it to ejs templates
res.render("vendors/show",{ event:events, promotion:promotions vendor:vendors});
}
});
});
Show.ejs code as
<%=vendor.name%>
<%=promotion.name%>
<%=event.name%>
You need to handle the async process. In your current code, there are three async processes. Please go through following the link you will definitely get an idea about it.
https://blog.risingstack.com/mastering-async-await-in-nodejs/
if you need more help Don’t hesitate to comment.
I managed to get all data using the below function. But now I have another issue, need to filter all the Promotions and Events by vendor But it doesn't filter.
Data Schema
const neweventSchema = new mongoose.Schema({
name:String,
image:String,
imageId:String,
description:String,
vendor:String,
sdate:String,
edate:String,
});
const Event = mongoose.model("Event",neweventSchema);
module.exports = Event;
//SCHEMA SETUP===============================================================================================================
const newpromoSchema = new mongoose.Schema({
name:String,
image:String,
imageId:String,
vendor:String,
description:String,
sdate:String,
edate:String,
});
//compile into a model
const Promotion = mongoose.model('Promotion',newpromoSchema);
module.exports = Promotion;
//SCHEMA SETUP===============================================================================================================
const vendorSchema = new mongoose.Schema({
name:String,
image:String,
imageId:String,
});
const Vendor = mongoose.model("Vendor", vendorSchema);
module.exports = Vendor;
Routes as below
router.get("/vendors/:id", function (req, res) {
Vendor.find({}, function (err, allVendors) {
if (err) {
console.log(err);
} else {
// //Find Collection And Assign It To Object
Event.find({}, function (err, allEvents) {
if (err) {
console.log(err);
} else {
Promotion.find({}, function (err, allPromotions) {
if (err) {
console.log(err);
} else {
//find order collection and passing it to ejs templates
res.render("vendors/show", {event: allEvents, promo: allPromotions, vendor: allVendors});
}
});
}
});
}
});
});
EJS Show page But It doesn't get filtered.
<!-- Trying to filter all the events by vendor if event's vendor name == vendor name then show on the show page -->
<%if(event.vendor === vendor.name){%>
<div class = "container-fluid">
<div class = "row">
<%event.forEach(function(events){%>
<div class="col-sm-6 col col-md-4 col-lg-3">
<div class="thumbnail">
<img src ="</%=events.image%>" class="rounded" width="304" height="236">
<div class="caption">
<h4><%=events.name%></h4>
<p class="font-italic font-weight-bold text-muted">
<%=events.vendor%>
</p>
<p><strong>Start Date</strong><%=events.sdate%></p>
<p><strong>End Date</strong><%=events.edate%></p>
</div>
<p>Event Details <br>
<%=events.description.substring(0,100)%>
</p>
<p>
Find More
</p>
</div>
</div>
<%})%>
</div>
</div>
<%}%>
<!-- //Trying to filter all the promotions by vendor if vendor name= to promotions's vendor name only show in the show page-->
<%if(promo.vendor===vendor.name){%>
<div class = "container-fluid">
<div class = "row">
<%promo.forEach(function(promotions){%>
<div class="col-sm-6 col col-md-4 col-lg-3">
<div class="thumbnail">
<img src ="<%=promotions.image%>" class="rounded" width="304" height="236">
<div class="caption">
<h4><%=promotions.name%></h4>
<p class="font-italic font-weight-bold text-muted"> <%=promotions.vendor%></p>
<p><strong>Start Date</strong> <%=promotions.sdate%></p>
<p><strong>End Date</strong> <%=promotions.edate%></p>
</div>
<p>
<%=promotions.description.substring(0,100)%>
</p>
<p>
Find More
</p>
</div>
</div>
<%})%>
</div>
</div>
<%}%>
I am trying to pass multiple values into res.render to be passed into my handlebars template. It seems to fulfil the condition but it doesn't show the content.
I'm trying to show a special navbar and a users snippets (their data) if they're logged in. Otherwise I want to display my normal navbar and all snippets (data from all users).
I've got it working if I just hand in a single value, but I can't seem to hand in multiple values.
Express code:
router.route('/')
.get(function (request, response) {
if (request.session.name) {
Snippet.find({ user: request.session.name._id }, function (err, data) {
if (err) {
console.log(err)
}
data.forEach(function (element, err) {
if (err) {
console.log(err)
}
})
let context = {
snippets: data.map(function (snippet) {
return {
_id: snippet._id,
user: snippet.user,
title: snippet.title,
body: snippet.body,
createdAt: snippet.createdAt
}
})
}
response.render('home/index', {loggedIn: true, context: context}) //Here is the issue
})
}
Handlebars template:
<ul class="people_list">
{{#if loggedIn}}
{{!-- Logged in --}}
{{#each snippets}}
<h2><i class="star fa fa-star-o" aria-hidden="true"></i> {{title}}</h2>
<h4>{{ createdAt }}</h4>
<pre>
<code class="language-javascript">{{body}}</code>
</pre>
<hr />
{{/each}}
{{else}}
{{!-- Not logged in --}}
{{#each snippets}}
<h2><i class="star fa fa-star-o" aria-hidden="true"></i> {{title}}</h2>
<h4>{{ createdAt }}</h4>
<pre>
<code class="language-javascript">{{body}}</code>
</pre>
<hr />
{{/each}}
{{/if}}
</ul>
You're passing in an array called context but referring to it as snippets in the handlebars code.
Either change your handlebars template to called {{#each context}} or change your nodejs code to response.render('home/index', {loggedIn: true, snippets: context}).