How to access parent modules when doing layered routing in mongoDB - node.js

As a personal project, I'm trying to build a social media site for teddy bear collectors. I would like users to be able to make a "collection" page which they can populate with individual profile pages for each of their bears. Finally, I would like other users to be able to comment on both the collection page and the individual profile pages.
However, I'm running into an error on the "/new" route for comments on the individual profile pages. I can't get it to find the id for the parent collection.
Below is the code I'm working with. I start by finding the id for the collection, then I get try to get the id for the bear (individual profile page). However, the process keeps getting caught at the first step.
var express = require("express");
var router = express.Router({mergeParams: true});
var Bear = require("../models/bear");
var Comment = require("../models/comment");
var Collection = require("../models/collection");
var middleware = require("../middleware");
//comments new
router.get("/new", middleware.isLoggedIn, function(req, res) {
Collection.findById(req.params.id, function(err, foundCollection) {
if(err || !foundCollection){
req.flash("error", "Collection not found");
return res.redirect("back");
}
Bear.findById(req.params.bear_id, function(err, foundBear) {
if(err){
res.redirect("back");
} else {
res.render("bcomments/new", {collection_id: req.params.id, bear: foundBear, collection: foundCollection});
}
});
});
});
//bcomments create
router.post("/", middleware.isLoggedIn, function(req, res){
Collection.findById(req.params.id, function(err, foundCollection) {
if(err || !foundCollection){
req.flash("error", "Collection not found");
return res.redirect("back");
}
//look up bear using id
Bear.findById(req.params.id, function(err, foundBear){
if(err){
console.log(err);
res.redirect("/bears" + bear._id);
} else {
//create new comment
Comment.create(req.body.comment, function(err, comment){
if(err){
req.flash("error", "Something went wrong");
console.log(err);
} else {
//add username and id to comment
comment.author.id = req.user._id;
comment.author.username = req.user.username;
//save comment
comment.save();
//connect new comment to bear
bear.comments.push(comment);
bear.save();
//redirect bear show page
req.flash("success", "Successfully added comment");
res.redirect("/collections/" + foundCollection._id + "/bears/" + foundBear._id);
}
});
}
});
});
});
So, instead of rendering the new comment form, it hits a "null" error and redirects back at the first if statement.
If anyone can help me figure this out, I'd be exceedingly grateful.
Thank you.

I think the problem is that you are defining the path "/new" (which has no parameters) and trying to access req.params.id. If you expect to have the parameter id you should define it in the path like this: router.get("/new/:id", .... Check the Express oficial documentation for more details.
EDIT:
You may have mixed req.params with req.query and req.body. If you are passing parameters in the request, you must access them through req.query (for example: req.query.id or req.query.bear_id) in the case of GET and DELETE or through req.body in POST and PUT.

Related

GET request with different mongo find results on same page

I would like my website to have a search bar in the top section that returns a single document (ink) from a mongo database. On the same page, I would like to be able to access all documents from the same database.
I'm having trouble trying to figure out how to do this on one page, since I can only send one result to URL.
Is there some way to send all documents to the page, then do a search with AJAX on the client side? I'm new to coding, and wondering if I'm going about this wrong.
I appreciate any help. Here is part of my code that sends the results I want, but to different pages.
app.get("/", function(req, res){
// FIND ONE INK FROM DB
var noMatch = null;
if(req.query.search) {
Ink.find({ink: req.query.search}, function(err, foundInk){
if(err){
console.log(err);
} else {
if(foundInk.length < 1) {
noMatch = "No match, please try again.";
}
res.render('new-index', {ink: foundInk, locationArray: locationArray, noMatch: noMatch })
}
});
} else {
// FIND ALL INKS FROM DB
Ink.find({}, function(err, allInks){
if(err){
console.log(err);
} else {
res.render("index", {ink: allInks, locationArray: locationArray, noMatch: noMatch });
}
});
}
});
You can use separated endpoints for each request. For the full access request, you can render the page, calling res.render, and for the search request, you can return a json calling res.json. Something like this:
app.get("/", function(req, res){
// FIND ALL INKS FROM DB
Ink.find({}, function(err, allInks){
if(err){
console.log(err);
} else {
res.render("index", {ink: allInks, locationArray: locationArray, noMatch: noMatch })
}
});
})
app.get("/search", function(req, res) {
// FIND ONE INK FROM DB
var noMatch = null;
Ink.findOne({ink: req.query.search}, function(err, foundInk){
if(err){
console.log(err);
} else
if(!foundInk) {
noMatch = "No match, please try again.";
}
res.json({ink: foundInk, locationArray: locationArray, noMatch: noMatch })
}
});
});
Note the call to Ink.findOne in the /search handler, which will return only one document.
This way you can make and AJAX request to /search, and parse the json returned from the server.
I've created a sample repository with the exact same issue here
Ideally you make an endpoint like this.
( id parameter is optional here...thats why the '?' )
www.example.com/api/inks/:id?
// return all the inks
www.example.com/api/inks
// return a specific ink with id=2
www.example.com/api/inks/2
So now you can render all the links via /inks and search a particular ink by using the endpoint /ink/:id?
Hope this helps !

ExpressJS: How to send data to URL

I have a post request which redirects to a new route as you can see
//POST login
app.post('/login', urlencodedParser, function(req,res){
userLogin(req.body, function(response){
if(response == true){
//Activate player login in db
loginPlayerDB(req.body.username);
getPlayerId(req.body.username, function(id){
res.redirect(req.baseUrl + '/:id/profile');
});
} else{
res.send(500, "Username or Password is incorrect");
}
})
});
This function gets called
function getPlayerId(username, callback){
let sql = "SELECT PlayerId FROM users WHERE Username = (?)";
var values = [username];
db.query(sql, values, function(err, result, fields){
if(err){
console.log(err);
} else{
return callback(result);
}
})
}
It redirects to this route
app.get('/:id/profile', function(req,res){
res.render('profile/profile');
})
Everything works great except the URL on the new page is
http://localhost:3000/:id/profile
When it should be something like
http://localhost:3000/6/profile
If the player has an id of 6. How do I fix this
//Solution.
Thank you to MadWard for the help. My mistake was that in my getPlayerId function, I return result, which is an array, instead of result[0].PlayerId in order to get the specific id I was looking for
You are literally redirecting to /:id/profile. This is a string, it is fixed, and will always be '/:id/profile'.
What you want to do instead is, using template strings:
getPlayerId(req.body.username, function(id){
res.redirect(`${req.baseUrl}/${id}/profile`);
});
Using normal string concatenating like you were doing:
getPlayerId(req.body.username, function(id){
res.redirect(req.baseUrl + '/' + id + '/profile');
});

Unable to render user object to EJS view header.ejs

I am new to EJS and Node.js. I have been struggling to send the user object data to ejs template page. Actually, I want to display the current user's name who logged into the portal.
This is some portion of app.js
app.use(function(req,res,next){
res.locals.success_msg=req.flash('success_msg');
res.locals.error_msg=req.flash('error_msg');
res.locals.not_registered_user_msg=req.flash('not_registered_user_msg');
res.locals.authdata = firebase.auth().currentUser;
next();
});
//Get user info
app.get('*',function(req,res,next){
if(firebase.auth().currentUser!=null){
var id = firebase.auth().currentUser.uid;
var profileRef = fbRef.child('users');
var query = profileRef.orderByChild('uid').equalTo(id);
query.once('value', function(snap){
var user = JSON.stringify(snap.val());
console.log('User data is: '+user);
res.locals.user = user;
});
}
next();
});
Here is the header.ejs where I want to retrieve and display current user's name:
I am able to enter into the if condition., and able to print the phrase "Welcome,". But unable to get the actual user data, it simply showing empty space like this
please suggest me, where did i gone wrong here! Thanks in advance.
As per sugegstion i am adding logs from console here. I am getting user data from firebase including child id as marked here.
This doesn't look right to me:
var user = JSON.stringify(snap.val());
console.log('User data is: '+user);
res.locals.user = user;
You're converting the user object into a JSON string. Try this instead:
var user = snap.val();
console.log('User data is: ' + JSON.stringify(user));
res.locals.user = user;
I'm not really familiar with Firebase but I'd also guess that once is asynchronous, so you'd need to wait before calling next, a bit like this:
if (firebase.auth().currentUser != null) {
// ... This bit the same as before ...
query.once('value', function(snap) {
var user = snap.val();
console.log('User data is: ' + JSON.stringify(user));
res.locals.user = user;
next();
});
}
else {
next();
}
If that still doesn't work, could you include the output of the console logging in your question?
From reading over the express documentation, it seems that the arguments passed into the request handler are req and res.
You do not get the next function as an argument. With that said, you need to change your app.get to an app.use which is where you get the actual next function.
app.use((req, res, next) => {
if (firebase.auth().currentUser) {
const { uid } = firebase.auth().currentUser
const profileRef = fbRef.child('users')
const query = profileRef.orderByChild('uid').equalTo(uid);
query.once('value', snap => {
const user = JSON.stringify(snap.val())
res.locals.user = user
})
}
next()
})

Express: Referencing the parent within the route definition for a child

I am currently studying https://thinkster.io/mean-stack-tutorial/, but am puzzled about the route definition for a child element. For example, in the following...
// create a new comment
router.post('/posts/:post/comments', function(req, res, next) {
var comment = new Comment(req.body);
comment.post = req.post;
comment.save(function(err, comment){
if(err){ return next(err); }
req.post.comments.push(comment);
req.post.save(function(err, post) {
if(err){ return next(err); }
res.json(comment);
});
});
});
... we are trying to define the route for adding a new comment for a particular post. As you can see, we are required to link the parent to the comment...
comment.post = req.post;
... and we are also required to, understandably, save the parent within the comment save function. However, when comes to updating the comment (i.e. increasing the upvotes for a comment) we are only required to do...
// upvote a comment
router.put('/posts/:post/comments/:comment/upvote', function(req, res, next) {
req.comment.upvote(function(err, comment){
if (err) { return next(err); }
res.json(comment);
});
});
... where 'upvote' is a custom schema method...
CommentSchema.methods.upvote = function(cb) {
this.upvotes += 1;
this.save(cb);
};
... why is that we are not required to reference the parent object 'post' here at all? I mean how does the system know exactly which comment to load? I understand that ':post' refers to the parent post, however, we are not even referencing that pre-loaded parent within the method... so how is it being used? Are the correct set of comments automatically loaded from the pre-loaded post parent object, without our need to do this explicitly?
Any guidance would be appreciated.
according with the tutorial :
router.param('post', function(req, res, next, id) {
var query = Post.findById(id);
query.exec(function (err, post){
if (err) { return next(err); }
if (!post) { return next(new Error('can\'t find post')); }
req.post = post;
return next();
});
});
This code will take params from route: post/:post , so basically if you test the route : post/1 , it will set into request object(req) the post with id:1

How to control a PUT request with NodeJS Express

I need to set a ReST API with my NodeJS Express 4 Application.
Currently, this is my API.
I have a families resource which exposes several HTTP verb.
GET to perform a read in my MongoDB database.
GET with familyID to get the family with the id familyID
POST to create a new family in the database.
PUT to update a family.
I want to follow the ReSTful theory so I'd like to control when a PUT is done that all the resource is modified and not a part of it (which is a PATCH verb).
This my nodejs route controller code :
// Main Function
router.param('famillyId', function(req, res, next, famillyId) {
// typically we might sanity check that famillyId is of the right format
Familly.findById(famillyId, function(err, familly) {
if (err) return next(err);
if (!familly) {
errMessage = 'familly with id ' + famillyId + ' is not found.';
console.log(errMessage);
return next(res.status(404).json({
message: errMessage
}));
}
req.familly = familly;
next();
});
});
/PUT
router.put('/:famillyId', function(req, res, next) {
console.log('Update a familly %s (PUT with /:famillyId).', req.params.famillyId);
req.familly.surname = req.body.surname;
req.familly.firstname = req.body.firstname;
req.familly.email = req.body.email;
req.familly.children = req.body.children;
req.familly.save(function(err, familly) {
if (err) {
return next(err);
}
res.status(200).json(familly);
});
});
I'd like to know what is the best way to do this control. I don't want to use a series of 'if' for each record of my JSON object. Is there an automatic way of doing it ?
Just to avoid this kind of code :
if (req.familly.surname)
if (! req.body.surname)
return next(res.status(200).json('{"message":"surname is mandatory"}‘)));
Doing this kind of things for each property in my JSON Object is very boring, lots of code to type for nothing.
I looking forward a clean code to do it.
Thanks.
Hervé
var control = ['surname', 'firstname', 'email', 'children'];
control.forEach(function(arg){
if(!req.body[arg]){
return next(res.status(200).json({"message": arg + " is mandatory"}));
}
});

Resources