Displaying content from different mongoose tables on one view - node.js

I'm a (relatively inexperienced) Java EE developer who is looking to learn node.js. I'm working with the express framework, mongodb, and the mongoose framework. I've been working on building a simple blog site (just for practice) with an mvc like architecture. It would have would have 4 mongodb collections: post, image, user, comment. The basic Schemas are as follows:
postSchema = mongoose.Schema({
id: Number,
dateCreated, {type: Date, default: Date.now}
title: String,
content: String
});
var Post = mongoose.model('Post', postSchema);
imageSchema = mongoose.Schema({
id: Number,
postId: Number,
path: String
});
var Image = mongoose.model('Image', imageSchema);
userSchema = mongoose.Schema({
id: Number,
username: String,
password: String,
email: String
});
var User = mongoose.model('User', userSchema);
commentSchema = mongoose.Schema({
id: Number,
postId: Number,
userId: Number,
dateCreated: {type: Date, default: Date.now},
content: String
});
var Comment = mongoose.model('Comment', commentSchema);
I want to be able to show a post, an image, comments, and user info all on one page. My issue is that I can't quite figure out how retrieve and send all this data in an asynchronous way. This seems to be what most of the examples I have found do (not necessarily all in one file):
app.get('/', function(res, req) {
Post.findOne(function(err, post) {
if (err) return res.send(500);
res.render('index', post);
});
});
This wouldn't work for me because I would info from the image, comment, and user collections as well. Is there an asynchronous way to do this? If not is there a way to reconfigure what I have so that it could be asynchronous? (I'm trying to get a feel for asynchronous programming.)
Thanks in advance!

Simples way to do this as-is would be to use promises and perform simultaneous async operations:
Post.findOne(id).then(post => {
let postId = post.id;
return Promise.all([
Comments.find({postId}),
Images.find({postId}),
// Not sure what query you need here
User.find(),
post,
]);
}).then(data => {
let [comments, images, users, post] = data;
res.render('index', {comments, images, users, post});
});
In your index template you would have an object with the four properties.
You can perform simultaneous async operations without promises, but I'll leave that for someone else to talk about. I would prefer to work with promises.
Mongoose will also allow you to use other schema definitions as data types as in:
commentSchema = mongoose.Schema({
id: Number,
postId: Post,
userId: User,
dateCreated: {type: Date, default: Date.now},
content: String
});
In this case you can use .populate after some queries in order to perform another query under the hood -- e.g. get the post data for a comment.
Since you are using MongoDB -- a NoSQL Database -- I would look into denormalizing the data and keeping it flat. Firebase, which stores data in a similar structure has a great article on how to store and use denormalized data

Related

how can i get data from an db object which is store id of another db in mongoDB

hi there I am not getting data from a DB object which is storing the id of another DBS, actually, this is a blog website so I am posting comments and acting comments but there are problems I am getting the same comments on all posts but I want, every post should have their own comment.
here is post schema
const blogSchema = new mongoose.Schema({
title: String ,
content: String,
image:{data: Buffer,contentType: String},
comment:[
{
type:mongoose.Schema.Types.ObjectId, ref:'Comment'
}
]
});
here is the comment schema
var commentSchema = new mongoose.Schema({
name:{
type:String,
required: "this field is required"
},
comment:{
type:String,
required:"this filed is required"
},
blog:{
type:mongoose.Schema.Types.ObjectId,
ref: 'Blog'
}
},{
timestamps: true
})
node js router which is getting post but not comment
pp.get("/post/:postname",(req,res)=>{
// const requesttitle = _.lowerCase(req.params.postname);
const requesttitle = req.params.postname;
Blog.findOne({_id: requesttitle} ,(err ,got)=>{
if(err){
console.log(err)
}else{
const data= got.comment.find({})
console.log(data)
res.render('post',{post:got });
}
})
})
I believe the problem lays in your Schema. In your blogSchema you have references to many Comment documents, and in your commentSchema you have a reference to a single "Blog" ( I suggest not naming it "blog" but "post" since that is what it is ) . This duplicative referencation is not necessary in most cases.
Since in your setup a single comment can only be a child of one specific post, this would be the reference I would go for. The post document itself doesn't really need to know directly what comments are included since that information is already hold in the Comment document.
For your post I would suggest the following schema :
// Post Schema
const postSchema = new mongoose.Schema({
title: String ,
content: String,
image: { data: Buffer, contentType: String }
});
For your comment I would suggest the following schema :
// Comment Schema
const commentSchema = new mongoose.Schema({
name: {
type: String,
required: "this field is required"
},
comment: {
type: String,
required: "this filed is required"
},
post: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}
});
Now sure the next step depends on your whole frontend part is setup up, but having schemas like this would let you do something along the lines of :
pp.get("/post/:id", async (req,res) => {
const id = req.params.id;
const post = await Post.findOne({ _id: id });
const comments = await Comment.find({ post: id });
res.render('post', {
post: post,
comments: comments
});
});
Pros
one-directional relation means less work if a comment is created or deleted.
possibility to just get comment and/or post or both in one api call.
Cons
Requires 2 database calls if post and comments both are requested.
Alternative: Subdocuments
As an alternative to using referenced Documents you can use Subdocuments.
For your commentSchema that means you won't need to create a seperate Model out of it. However your postSchema would need to look like this:
const commentSchema = new mongoose.Schema({
message : { type : String }
});
const postSchema = new mongoose.Schema({
comments : [commentSchema]
});
👆 This would by default include all comments of the post if you retrieve the post from the database. However it would also require a different code for interacting with those comments (adding, deleting, ...) but you can read about it in the docs I am sure.

Why is the find method not working in Mongoose

I am working on a express app with mongoose.
the .find method is working in other route but inside my blogs route, it is not working.
Here is my mongoose model:
const mongoose = require('mongoose');
let BlogSchema = new mongoose.Schema({
title: {type: String, unique: true, required: true},
body: {type: String},
category: String,
permalink: String,
date: {type: Date, default: Date.now},
image: {type: String},
imageId: String,
tags:[{type: String}],
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
}
});
let Blog = mongoose.model('Blog', BlogSchema);
module.exports = Blog;
and here is my router.get :
//SHOW ROUTE
router.get('/blog/:category/:permalink', (req,res)=>{
console.log(req.params) //{ category: 'travel', permalink: 'why-travel-to-places' }
Blog.find({category: req.params.category,permalink: req.params.permalink}, (err, foundPost)=> {
if (err){
console.log(err)
} else {
console.log(foundPost) // []
res.render('blog/show', {post: foundPost});
}
});
});
As you can see, I console.log req.params and foundPost .
req.params result is { category: 'travel', permalink: 'why-travel-to-el-nido' } and foundPost is an empty array []
Now I looked at mongo using db.blogs.find({category: "travel"}).pretty() and it found it. But inside my show route ,it is not finding it?
I looked up on other questions related to my problem and someone said that perhaps it has a problem with schema, but in my case, it's not.
my .find() is working in in other routes but in it's not working in this route.
Did I miss something, please help. Thank you
I'm pretty sure that the .find() is working correctly, but is not finding any document in the database with that query criteria. Note that in the route code you are querying { category: 'travel', permalink: 'why-travel-to-el-nido' }, i.e that the matched Blog document must have the fields category: 'travel' AND permalink: 'why-travel-to-el-nido', but when you look at Mongo using db.blogs.find({category: "travel"}).pretty() you are only looking for Blog documents that have {category: "travel"}, but no restriction for the permalink field.
Please, search again in Mongo using db.blogs.find({category: "travel", permalink: "why-travel-to-el-nido"}).pretty() to simulate the same query from the route, and see if you can find any document that matches that criteria.
My guess is that you won't find any, so the mongoose .find() is returning an empty array, meaning "no matches".
Edit: I just saw the comment from Neil Lunn, and it might be possible that the mongoose configuration is not pointing to the right database. Follow his instructions to make sure you are querying to the collection you want.

Mongoose can't find any elements after changing property type

I originally have these two schemas:
var UserSchema = new Schema({
first: String,
last: String
});
var SaleSchema = new Schema({
createdAt: Date,
registeredBy: { type: Schema.ObjectId, ref: 'User' }
});
But I want to edit my SaleSchema to save the user name instead of the ID, so I changed it for:
var SaleSchema = new Schema({
createdAt: Date,
registeredBy: String
});
Next, I wanted to edit all the Sales documents and replace the user IDs on registeredBy for the user's full name, but I can't seem to be able to perform a find query for the old ID's.
Long story short, this query returns no matches on mongoose, but it works perfectly using the mongo console:
Mongoose
Sale.find({ registeredBy: '57ea0cbb47431f0b43b87d42' })
.then(results => res.json(results))
.catch(err => res.status(500).json(err));
// result: []
MongoDB console
db.sales.find({ registeredBy: '57ea0cbb47431f0b43b87d42' })
// result: 8 elements
After I modify my schema's property back to ObjectId, the mongoose query works again. Since I need to migrate to a new datatype, I want to be able to query and store both types of values. Is this possible?
Good question this is a complicated edge case. I am not super familiar with Mongoose specifically, but one way to do this would be to migrate your data at a lower level. For example, create a mongo script that uses the low-level mongo API to do the migration, something along the lines of:
db.sales.find().forEach(function(doc){
var user = db.users.find({ _id: doc.registeredBy });
db.sales.update({ _id: doc._id, }, {
$set: { registeredBy: user.first + ' ' + user.last }
});
});
This is similar to what a module like https://github.com/balmasi/migrate-mongoose does, but I've personally found it easier to use mongo scripts on the cli directly.
mongo < sale-schema-migration.js

mongoose: Insert a subdocument NOT an array

I am struggling to insert a document inside another document. I've looked at all the entries like this but they aren't quite what I am looking for.
Here is the scenario:
I have a common document that has its own schema. Lets call it a related record:
(function(){
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var relatedRecordSchema = new Schema({
params: {
recordId: Schema.Types.ObjectId,
recordType: String,
recordTitle: String
},
metadata: {
dateCreated: {type: Date, default: Date.now}
}
},{ _id : false });
mongoose.model('RelatedRecord', relatedRecordSchema);
})();
I have no trouble inserting this in an ARRAY inside document that require it. I.e its configured this way:
//Embedded
relationships: {
following: [mongoose.model('RelatedRecord').schema],
followers: [mongoose.model('RelatedRecord').schema],
blocked: [mongoose.model('RelatedRecord').schema]
}
This works perfectly.
The scenario that does not work is where there is a single related record, lets say the source of a notification:
var notificationSchema = new Schema({
params: {
title: String,
imageUrl: String,
source: mongoose.model('RelatedRecord').schema
},
metadata: {
dateCreated: { type: Date, default: Date.now },
dateViewed: Date
}
});
So when I am creating the notification I try and assign the previously prepared RelatedRecord
returnObj.params.source = relatedRecord;
The record appears during a debug to be inserted (it is inside a _docs branch but far deeper than I would expect) but when the object is saved (returnObj.save()) the save routine is abandoned without error, meaning it does not enter into the callback at all.
So it looks to me that i'm confusing mongoose as the dot assignment is forcing the subdoc into the wrong location.
So the question is simple:
How do I set that subdocument?
What the question isn't:
No I don't want to populate or advice on how you would solve this problem differently. We have sensible reasons for doing things how we are doing them.
Cheers
b
As Hiren S correctly pointed out:
1) Sub-Docs = array, always. Its in the first line in the docs :|
2) By setting the type to mixed, assignment of the object worked.
I'm a dumdum.

How do I update a property with the current date in a Mongoose schema on every save?

In my database collections, I want to update a 'lastChanged' field every time the record is updated with the current datetime. I want it to be in the same format as mongoose's default date like:
ISODate("2011-10-06T14: 01: 31.106Z")
Any words of wisdom?
If you just want an ISO String use:
new Date().toISOString()
One way of accomplishing this is to use Mongoose Middleware and update the field pre-save.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
//schema
var SomethingSchema = new Schema({
text: {type: String},
createdAt: {type: Date, default: Date.now},
updatedAt: {type: Date, default: Date.now}
});
//middle ware in serial
SomethingSchema.pre('save', function preSave(next){
var something = this;
something.updatedAt(Date.now());
next();
});
It seems, however, that the middleware is not always invoked:
Notes on findAndUpdate()
pre and post are not called for update operations executed directly on the database, including Model.update,.findByIdAndUpdate,.findOneAndUpdate, .findOneAndRemove,and .findByIdAndRemove.order to utilize pre or post middleware, you should find() the document, and call the init, validate, save, or remove functions on the document. See explanation.
Update: See this question "add created_at and updated_at fields to mongoose schemas"
In a few days Mongo is going to announce new 2.6 version (currently you can download experimental 2.5.x version). Among many other features you can use $currentDate which is going to do exactly the thing you want:
db.users.update(
<criteria>,
{
$currentDate: { yourField: true},
}
)
The middleware function is a good approach, however, it should be
SomethingSchema.pre('save', function preSave(next){
var something = this;
something.updatedAt = Date.now();
next();
});
Since something.updateAt is not a function.
I added updated: new Date to fix a similar problem. Here is how I used it.
update: (req, res) => {
let userId = req.params.id;
let userParams = {
name: {
first: req.body.first,
last: req.body.last
},
email: req.body.email,
password: req.body.password,
updated: new Date
};
db.User.findOneAndUpdate(userId, { $set: userParams })
.then(upUser => res.json(`Profile Updated for: ${upUser.fullName}`))
.catch(err => res.status(422).json(err));
}

Resources