I'm new to Javascript and I'm trying to learn express and create an application that will allow users to create new recipes, browse existing recipes, and view recipes.
I've got my server running by typing recipeserver.js in the cmd bar and then typing localhost:3000 in my address bar on google chrome. So far it loads the index.html homepage and from there, I am able to click on a link titled "Create a Recipe" which leads me to the create.html page that looks like this:
create.html page
Initially, there will be only three recipes on the server, which are included in the database object within the recipeserver.js code I've included below. The create.html page allows a user to enter recipe information. When the Save Recipe button is clicked, the addrecipe.js file is supposed to send the recipe data to the server using a POST request to the resource /recipes
Within the server code, all recipes will be stored in a single object called database. The keys of this object will be unique IDs and the values will be the recipes associated with those IDs. I'm stuck on a task where I'm supposed to add a route within the server code to handle POST requests to the /recipes resource. The handler for this route should:
Extract the recipe object included in the POST request body
Generate a unique ID for the new recipe (Etc. a basic integer that increases every time a recipe is added.)
Add a new entry into the recipes object with the key being the unique ID and the value being the recipe object.
When testing my code by adding a few recipes to my server, I should be able to just log the contents of the recipes object to see that it is storing the correct data, like in the picture below (this picture isn't mine):
load recipe in cmd
So as shown in the first picture of my screen, I filled in the contents of the recipe I want to add in create.html. When I click on the "Save Recipe" button however, instead of loading the contents of the recipe into my cmd window, I get the error:
TypeError: C:\Downloads\recipeApplication\views\recipes.pug:8
6| div#main
7| h1 List of Recipes:
> 8| each recipe in recipes
9| a(href="/recipes/" + recipe.id) #{recipe.name}
10| br
11|
Cannot read property 'length' of undefined
I'm a little stumped on how to a route within the server code to handle POST requests to the /recipes resource. I made a function called loadRecipes() that I'm trying to do all this in. I attempted to create a new id and increment it by 1 like the task suggests. I'm having trouble extracting the recipe object included in the POST request body. I attempted this and ended up commenting it out as it created the same error. I'm just trying to get the Save Recipe button to work so that the recipe that is added prints its contents in the cmd bar like in the 2nd picture, but I'm really lost and overwhelmed with the amount of information that comes up when I try to search for a solution and would appreciate some help in getting this to work.
Here's all my code incase anyone wants to run it but I believe my problem just lies in the recipeserver.js file. When the Save Recipe button is clicked, the addrecipe.js file sends the recipe data to the server using a POST request to the resource /recipes.
recipeserver.js:
const express = require('express');
const fs = require("fs");
const shortId = require("short-id");
const session = require('express-session');
const app = express();
const pug = require("pug");
const port = 3000;
let database = {
"0":{
"ingredients":
[
{"name":"Crab","unit":"Tsp","amount":3},
{"name":"Peas","unit":"Cup","amount":12},
{"name":"Basil","unit":"Tbsp","amount":10},
{"name":"Cumin","unit":"Liter","amount":3},
{"name":"Salt","unit":"Tbsp","amount":1}
],
"name":"Boiled Crab with Peas",
"preptime":"13",
"cooktime":"78",
"description":"A boring recipe using Crab and Peas",
"id":"0"
},
"1":{
"ingredients":
[
{"name":"Peanuts","unit":"Liter","amount":10},
{"name":"Artichoke","unit":"Tsp","amount":3},
{"name":"Basil","unit":"Cup","amount":11},
{"name":"Sage","unit":"Grams","amount":13},
{"name":"Pepper","unit":"Cup","amount":1}
],
"name":"Boiled Peanuts with Artichoke",
"preptime":"73",
"cooktime":"74",
"description":"A exciting recipe using Peanuts and Artichoke",
"id":"1"
},
"2":{
"ingredients":
[
{"name":"Lobster","unit":"Tsp","amount":14},
{"name":"Brussel Sprouts","unit":"Liter","amount":14},
{"name":"Sage","unit":"Tbsp","amount":3},
{"name":"Thyme","unit":"Tbsp","amount":12},
{"name":"Pepper","unit":"Tsp","amount":10},
{"name":"Cumin","unit":"Tbsp","amount":11}
],
"name":"Spicy Lobster with Brussel Sprouts",
"preptime":"86",
"cooktime":"19",
"description":"A tasty recipe using Lobster and Brussel Sprouts",
"id":"2"
}
}
let recipes = {};
for (let recipe in database) {
recipes[recipe.id] = recipe;
};
app.set("view engine", "pug");
app.use(express.static("public"));
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.get("/addrecipe.js", getAddRecipeJS);
app.get("/recipes", loadRecipes);
app.get("/recipe", loadRecipe);
app.route("/recipes", loadRecipes);
app.post("/recipes", loadRecipes);
let id = 1;
function loadRecipes(request, response, next){
response.status(200).render("recipes.pug", {"session": request.session});
/*
console.log("Request received!", request.body);
const newId = shortId.generate();
recipes[newId] = {
id: newId,
type: "recipe",
...request.body,
};
response.sendStatus(201);
*/
id++;
}
function loadRecipe(req, res, next){
res.status(200).render("recipe.pug", {"session": req.session});
}
function getAddRecipeJS(req, res, next){
fs.readFile("addrecipe.js", function(err, data){
if(err){
res.statusCode = 500;
res.end("Error reading file.");
return;
}
res.status(200).send(data);
return;
});
}
app.listen(port);
console.log(`Server listening at http://localhost:${port}`);
index.html:
<html>
<head><title>Recipe App Home Page</title></head>
<body>
<h1>Welcome to the Recipe App</h1>
<br>
Create a Recipe<br>
Browse Recipes<br>
</body>
</html>
create.html:
<html>
<head><title>Create a Recipe</title></head>
<body>
<script src="/js/addrecipe.js"></script>
<button type="button" onclick="genRandom()">Generate Random Recipe Data</button>
<button type="button" onclick="submit()">Save Recipe</button>
<br><br>
Recipe Name: <input type="textbox" id="recipename" size="50"><br>
Prep Time: <input type="textbox" id="preptime" size="50"><br>
Cook Time: <input type="textbox" id="cooktime" size="50"><br>
Description: <textarea rows="5" cols="50" id="description"></textarea><br><br>
Add ingredients:<br>
Unit: <select id="unit">
<option value="Tsp">Teaspoon</option>
<option value="Tbsp">Tbsp</option>
<option value="Cup">Cup</option>
<option value="Liter">Liter</option>
<option value="Gram">Gram</option>
</select><br>
Amount: <input type="textbox" id="amount"><br>
Ingredient: <input type="textbox" id="ingredient"><br>
<button type="button" id="add" onclick="addIngredient()">Add Ingredient</button>
<br><br>
<div id="ingredients">
</div><br>
<button type="button" id="submit" onclick="submit()">Save Recipe</button>
</body>
</html>
addrecipe.js:
let descriptors = ["Sweet", "Spicy", "BBQ", "Braised", "Deconstructed", "Broiled", "Boiled", "Flambeed", "Raw", "Smoked", "Butterflied", "Cured", "Grilled", "Poached"];
let proteins = ["Chicken", "Beef", "Lobster", "Shrimp", "Crab", "Turkey", "Duck", "Tofu", "Chickpeas", "Lentils", "Peanuts", "Kangaroo", "Human", "Goose", "Fish", "Pork", "Eggs", "Deer"];
let accompany = ["Broccoli", "Carrots", "Peas", "Potato", "Kale", "Banana", "Artichoke", "Asparagus", "Beans", "Broccoli", "Brussel Sprouts", "Celery", "Melon", "Mushrooms", "Pumpkin"];
let spices = ["Salt", "Pepper", "Basil", "Thyme", "Sage", "Cumin"];
let mealDescriptors = ["tasty", "mediocre", "very good", "boring", "exciting", "delicious", "easy", "ridiculously complex"];
let units = ["Tbsp", "Tsp", "Cup", "Liter", "Grams"]
let recipe = {ingredients: []};
function addIngredient(){
let name = document.getElementById("ingredient").value;
let amount = document.getElementById("amount").value;
let unit = document.getElementById("unit").value;
let ingredient = {name, amount, unit};
recipe.ingredients.push(ingredient);
updateIngredients();
}
function updateIngredients(){
let innerHTML = "";
recipe.ingredients.forEach(ingredient => {
innerHTML += ingredient.amount + " " + ingredient.unit + " " + ingredient.name + "<br>";
});
document.getElementById("ingredients").innerHTML = innerHTML;
}
function submit(){
recipe.name = document.getElementById("recipename").value;
recipe.preptime = document.getElementById("preptime").value;
recipe.cooktime = document.getElementById("cooktime").value;
recipe.description = document.getElementById("description").value;
let req = new XMLHttpRequest();
req.onreadystatechange = function() {
if(this.readyState==4 && this.status==200){
alert("recipe saved");
}
}
//Send a POST request to the server containing the recipe data
req.open("POST", `/recipes`);
req.setRequestHeader("Content-Type", "application/json");
req.send(JSON.stringify(recipe));
}
recipes.pug:
html
head
title Recipes
body
a(href="/create.html") add a recipe
div#main
h1 List of Recipes:
each recipe in recipes
a(href="/recipes/" + recipe.id) #{recipe.name}
br
recipe.pug:
html
head
title #{recipe.name}
body
div#main
h1 #{recipe.name}
br
First of all, thanks for putting in effort in explaining your issue in detail. One suggestions, you can share the repo instead of snippets of code (since this is quite long, and structure of folder do affects how we can get it up running).
Nonetheless, the error you're getting is due to recipes in recipes.pug is actually undefined.
index.js
function loadRecipes(request, response, next) {
response
.status(200)
// Here, you only pass `session` object to the template engine
// So template engine does not know about `recipes`
// So `recipes` is undefined, and you can't loop it
.render('recipes.pug', { session: request.session});
id++;
}
recipes.pug
html
head
title Recipes
body
a(href="/create.html") add a recipe
div#main
h1 List of Recipes:
// You're having issue here, since `recipes` is not passed to the template
// engine, it will throw an error
each recipe in recipes
a(href="/recipes/" + recipe.id) #{recipe.name}
br
Update your index.js with this
function loadRecipes(request, response, next) {
response
.status(200)
.render('recipes.pug', { session: request.session, recipes: database });
id++;
}
Now, you should be able to view the /recipes page and continue to work on the project.
I note there are quite number of bug in your code.
app.get('/addrecipe.js', getAddRecipeJS);
app.get('/recipes', loadRecipes);
app.get('/recipe', loadRecipe);
app.route('/recipes', loadRecipes);
app.post('/recipes', loadRecipes);
Based on this list of route, you shouldn't be using the same function to handle POST and GET request for /recipes.
GET can be used to retrieve the list of recipes
POST should be used to handle the data submitted, and save it to the database variable inside your index.js
I will give you a simple way of doing this (You should really explore yourself too)
app.post('/recipes', saveRecipes);
function saveRecipes(req, res, next) {
// Data submitted from your page is available in `req.body`
console.log(req.body);
// I'm trying to get a new `key` for the database
// This is because you're using number as `key` for each recipe
// Can skip this and use some random uuid as well
const dbLength = Object.keys(database).length;
// Add this to the database variable, and you're DONE!
database.dbLength = req.body;
res.send('ok');
}
I need a little guidance with routing in my Node/Express app. Initially, I create a new business from the Business model (works fine). After creating the business, I want a separate route which adds the current FX rates offered by that business (these fields will then be updated daily). My business model looks like this (simplified for purpose of example):
let businessSchema = new mongoose.Schema({
name: String,
category: String,
longDescription: String,
images: [ {url: String, public_id: String} ],
usdHkd: { type: String, default: "" },
hkdUsd: { type: String, default: "" },
rateCreatedAt: {
type:Date,
default:Date.now
},
});
When the business is first created, only the name, category, longDesc and images are populated, with default values for the FX rate fields. That works fine using these routes:
/* GET business new /business/new */
router.get("/new", isLoggedIn, asyncErrorHandler(businessNew));
/* POST business create /business */
router.post('/', isLoggedIn, upload.fields([{ name: 'images', maxCount: 10 }]), asyncErrorHandler(businessCreate));
I then set up separate routes/controllers like this for subsequently adding the FX rates, but I don't think these are correctly defined:
/* GET business index /business */
router.get('/:id/remittance/new', asyncErrorHandler(remittanceNew));
/* GET business index /business */
router.put('/:id/remittance', asyncErrorHandler(remittanceCreate));
//Remittances New
async remittanceNew (req, res, next) {
let business = await Business.findById(req.params.id);
res.render('remittanceViews/newRemittance', { business });
},
//Business Update
async remittanceCreate (req, res, next) {
let business = await Business.findByIdAndUpdate(req.params.id, req.body.business);
console.log(business);
//update the post with any new properties
business.usdHkd = req.body.business.usdHkd;
business.hkdUsd = req.body.business.hkdUsd;
business.rateCreatedAt = req.body.business.rateCreatedAt;
//save the updated post in the db
business.save();
//redirect to show page
res.redirect(`/business/${business.id}`);
},
The error message I get when I try to update is:
Cannot read property 'usdHkd' of undefined
Can anyone please advise where I'm going wrong here? Thanks
The error message indicates that usdHkd's parent variable in undefined. Most probably, this error is coming from business.usdHkd in business.usdHkd = req.body.business.usdHkd; (you can confirm it by adding more console.log() lines around this line and checking the outputs).
If business.usdHkd = req.body.business.usdHkd; is giving error, that means, business is undefined. However, you don't need this line as business is already updated by findByIdAndUpdate.
READ: Model.findByIdAndUpdate() and Promises in Mongoose
//Business Update
async remittanceCreate (req, res, next) {
let business = await Business.findByIdAndUpdate(req.params.id, req.body.business);
console.log(business);
// Below code is not required since findByIdAndUpdate() will update your model
/*
//update the post with any new properties
business.usdHkd = req.body.business.usdHkd;
business.hkdUsd = req.body.business.hkdUsd;
business.rateCreatedAt = req.body.business.rateCreatedAt;
//save the updated post in the db
business.save();
*/
//redirect to show page
res.redirect(`/business/${business.id}`);
},
UPDATE
You told that business is defined, but it's not getting updated. The reason is findOneAndUpdate() requires new option to be set as true else findOneAndUpdate() returns the old object (before updating it -- in a sense). So, please change the first line of remittanceCreate() to:
let business = await Business.findByIdAndUpdate(req.params.id, req.body.business, {new: true});
I'm having a problem with using passport-ldap. I just can't seem to get anything other than Bad Request. I've got scripts working in Python and Bash, so I know the information is correct and maybe I'm just not seeing something. Any help would be great.
Here's a condensed version with just the bits necessary.
const passport = require('passport');
const LdapStrategy = require('passport-ldapauth');
var ldapOptions = {
server: {
url: <URL>,
bindDN: 'uid=<username>',
bindCredentials: '<password>',
searchBase: 'ou=Users, ou=Internal, o=<Corporate>,
searchAttributes: ['uid', 'mail', 'displayName'],
searchFilter: 'uid={{username}}'
}
}
passport.use('ldapauth', new LdapStrategy(ldapOptions));
router.post('/login', passport.authenticate('ldapauth', {session: false}), function(req, res) {
console.log('Hello');
});
The problem is in router.post, you cannon handle for a request without sand back something.
try res.send('somethings');
Best regards
I think this is related to how I've defined my schemas, but I can't seem to find where the bug is... I have an almost identical file set up that's working perfectly and I've unfortunately not been able to find a duplicate of this issue anywhere.
When sending an API request to my local Express instance via Postman, only the 'title' request body value is stored in the database. I am sending the following simple request to my route as Application/Json (thought the same happens when using x-www-form-urlencoded):
{
"postTitle": "title goes here",
"postContent": "body goes here",
"isPublished": true
}
This is clearly being registered in express, as if I log the object I can see this data (plus timestamps and an _id):
{ _id: 5b07d9c0b8124e0599079c04,
postTitle: 'title goes here',
postContent: 'body goes here',
isPublished: true,
createdAt: 2018-05-25T09:39:12.869Z,
updatedAt: 2018-05-25T09:39:12.869Z,
__v: 0 }
However, when I send a get request to my route on this object using its ID, I receive the following in response:
{ "_id": "5b07d9c0b8124e0599079c04" }
Likewise, if I send a request to list all objects, I receive the following response:
{
"posts": [
{
"_id": "5b07d9c0b8124e0599079c04"
},
{
"_id": "5b07d9c0b8124e0599079c03"
},
{
"_id": "5b07d9914f10ce058f137eba"
}
]
}
Weirdly, sometimes the post title sent as part of the response is included in the response, and sometimes it isn't.
My schema is as follows:
var postSchema = new Schema({
postTitle: String,
postContent: String,
isPublished: Boolean
},
{
timestamps: true
});
My post API route for POST requests is as follows:
router.post('/posts', (req, res, next) => {
var postTitle = req.body.postTitle;
var postContent = req.body.postContent;
var isPublished = req.body.isPublished;
var newPost = new Post({
postTitle: postTitle,
postContent: postContent,
isPublished: isPublished
});
newPost.save(function (error) {
if (error) {
console.log(error)
}
res.send({
success: true,
message: 'Post saved successfully!'
})
})
});
(If you're not using Router, you'll have 'app.post' instead of 'router.post') Again, this is a bit longwinded but everything works fine.
My GET route is as follows:
router.get('/posts', (req, res) => {
Post.find({}, 'title content published', function (error, posts) {
if (error) { console.error(error); }
res.send({
posts: posts
})
}).sort({_id:-1})
});
OK - so, by going through my code in detail I've figured out where I was going wrong and fixed the issue, however, in my searching I found very little in the way of results. I'm pretty new to Express, so I'm going to outline the cause of the issue and how I resolved it in order to potentially save someone else a bunch of time if they make the same stupid mistake.
Now, the issue I'm having results from the way I was retrieving the data and serving that in response to get requests. As an example, here's my GET route to list all of the objects.
I was entirely focusing on the post request and assuming it was a problem with the database. It turns out what I'd actually done, is in order to make my schemas and routes less confusing, I'd changed the names of the relevant variables. What I'd forgotten to do, however, is update this line in my GET route to reflect the change:
Post.find({}, 'postTitle postContent isPublished', function (error, posts) {
Which I'd left as:
Post.find({}, 'title content published', function (error, posts) {
The reason the title sometimes displayed is that I tried undoing changes back and forth to spot the issue.
I know this is a super basic query but I got stuck on this for the best part of a day, and the only other relevant discussion on this ended with OP saying that it magically fixed itself.
I am currently using PouchDB as my DB and I am using Cloudant for the remote service. I am currently trying to create document, however, when I invoke the function, I have errors.
May I know where did I do wrong? Could it be the URL wrong or my syntax is wrong?
Uncaught Reference Error: PouchDB is not a constructor
This is my javascript code
function pouchdb() {
var db = new PouchDB("todos");
var remoteDB = new PouchDB("http://example.cloudant.com/example");
window.PouchDB = db;
var doc = {
"_id": "Can123",
"name": "You123",
"occupation": "See1",
"age": 3,
"hobbies": [
"Watch 9pm show",
"chasing laser pointers",
"lookin' hella cute"
]
};
db.put(doc);
PouchDB.sync(db, remoteDB);
}
HTML code
<button onclick="pouchdb()">pouchdb</button>
Update
I changed my insert code for this set of code
function pouchdb() {
var db = new PouchDB("todos");
var remoteDB = new PouchDB("http://example.cloudant.com/example");
var todo = {
_id: "mittens1233",
title: "hello",
occupation: "kitten123"
};
db.put(todo, function callback(err, result) {
if (!err) {
console.log('Successfully posted a todo!');
}
});
}
The result i got back is Successfully posted a todo!, however, my cloudant dashboard still shows 0 doc. May I know why?
Remove that line
window.PouchDB = db;
I think that's the problem. Once you click the button, the global PouchDB turns the variable db, what makes it not be a constructor anymore.
If the error still continues, #gcampbell comment should be right?