Technology: MongoDB, ExpressJS
I have 3 schema
userSchema:
userSchema = {
name: {type: String},
password: {type: String},
email: {type: String},
friends: {type: [mongoose.Types.ObjectId]}
}
textPostSchema =
textPostSchema = {
text: {type: String},
postType: {type: String, default: "textPost"},
userId: {type: mongoose.Types.ObjectId}
}
articalPostSchema:
articalPostSchema = {
title: {type: String},
content: {type: String}
postType: {type: String, default: "articalPost"},
userId: {type: mongoose.Types.ObjectId}
}
now I have one social media application in which I have to show these two post when user's friend post's a post, and include infinite scroll. Both textPost and articalPost should be send if to frontend and only total 10 post should be sent at a time. How should I write a API for timeline?
output should look like:
{
post: [
{
title: "artical Post title",
content: "artical post content",
postType: "articalPost",
userId: "60b9c9801a2a2547de643ccd"
},
{
text: "text post ",
postType: "textPost",
userId: "60b9c9801a2a2547de643ccd"
},
... 8 more
]
}
UPDATE:
I got the solution:-
I created on more schema:
timelineSchema = {
postId: {
type: mongoose.Types.ObjectId,
required: true,
ref: function () {
switch (this.postCategoryType) {
case 'articleposts':
return 'ArticlePost';
case 'textposts':
return 'TextPost';
}
},
},
postCategoryType: {
type: String,
required: true,
},
userId: {
type: mongoose.Types.ObjectId,
required: true,
ref: 'User',
},
},
and then I created one function to get only friends post:
exports.getTimelinePosts = async (req, res) => {
try {
const timelinePosts = await TimelineModel.find({
userId: { $in: [...req.user.friends, req.params.id] },
})
.skip((req.params.page - 1) * 10)
.limit(10)
.sort({ createdAt: -1 })
.populate('postId');
return res.status(200).json({ status: 'success', data: timelinePosts });
} catch (error) {
return res.status(500).json(error);
}
};
To implement the pagination with Mongoose, You can do something like that.
const getPosts = async (userId, pageNumber) => {
let result = await Post.find({ userId })
.skip((pageNumber - 1) * 10)
.limit(10);
return result;
};
pageNumber is a counter that you need to pass from the frontend and will be incremented by 1 whenever a user hits the scroll limit.
If you want to query and merge data from multiple collections you need to update your schema to use populate. Just include ref where you are referring to other collections.
This may help.
https://mongoosejs.com/docs/populate.html
Assuming you are using express and mongoose. The code to fetch both,
// first bring all those schema from your mongoose models
const Article = require('./models/ArticleSchema');
const Text = require('./models/TextSchema');
const fetchArticleAndTextPost = async (req, res)=>{
//find all data
const articles = await Article.find();
const texts = await Text.find();
//join them together
const post = articles.concat(texts);
return res.status(200).json({
status: 200,
data: post,
})
}
Related
I am a nodejs newbie. I have two simple models, User and Story. Here is what I want to do:
I want to retrieve all stories that have {status:"public"} and store it in an array called retrievedStories.
Then for each story I want to use its "user" field (which contains the object id of the user) to lookup the name of the user from User
Then add a new key in each element of retrievedStories called authorName with the name of the user.
Here are the models:
const UserSchema = new mongoose.Schema({
googleId: {
type: String,
required: true
},
displayName: {
type: String,
required: true
},
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
image: {
type: String,
},
createdAt: {
type:Date,
default: Date.now()
}
})
const StorySchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
body: {
type: String,
required: true
},
status: {
type: String,
default: 'public',
enum: ['public', 'private']
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
createdAt: {
type:Date,
default: Date.now()
}
})
And here is what I tried, but doesn't work. The stories are retrieved but the authorName is not added. Any help (possibly a better way to do this?) will be highly appreciated!
router.get('/',async (req,res)=>{
try {
const retrievedStories = await Story.find(
{status: "public"}
)
await Promise.all(retrievedStories.map(async (story) =>{
const author = await User.findById(story.user)
story.authorName = author.displayName
}))
return res.json(retrievedStories)
} catch (error) {
console.log(error)
}
})
You can simplify your query by using populate to retrieve User's data:
router.get('/', async (req, res) => {
try {
const retrievedStories = await Story.find({ status: 'public' })
.populate('user')
.exec();
return res.json(retrievedStories);
} catch (error) {
console.log(error);
}
});
You can then access User's displayName data on each Story by accessing story.user.displayName.
For more information on query population see the official docs.
I'm quite new to node and mongoose. I'm trying to do a project using them, but i'm running into an error while trying to populate. The comment is saved to the Comment schema perfectly, but throws an error when i reference it Organization Schema.Please advise me on what i'm doing wrong. Any form of assistance will be appreciated.
// Post route for comment(on the Organization's profile page)
router.post('/comment/:id', ensureAuthenticated,(req, res) =>{
let id = req.params.id;
console.log(mongoose.Types.ObjectId.isValid(id))
const commentObject = new Comment({
sender: 'Fred kimani',
commentBody: req.body.commentBody
})
console.log(commentObject);
commentObject.save((err, result) =>{
if(err){console.log(err)}
else{
Organization.findByIdAndUpdate(id, {$push: {comments: result}}, {upsert: true}, (err, organization) =>{
if(err){console.log(err)}
else{
console.log('======Comments====')
}
})
res.redirect('/users/organizationprofilepage/:id')
}
})
});
//Organization Schema
const mongoose = require('mongoose');
const OrganizationSchema = new mongoose.Schema({
organization_name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
category: {
type: String,
required: true
},
isApproved: {
type: Boolean,
default: false
},
image:{
type:String,
required:true
},
description: {
type: String,
required: true,
},
comments: [{
type: mongoose.Types.ObjectId,
ref: 'Comment'
}],
},
//{ typeKey: '$type' }
);
OrganizationSchema.statics.getOrganizations = async function () {
try {
const organizations = await this.find();
return organizations;
} catch (error) {
throw error;
}
}
//defines the layout of the db schema
const Organization = mongoose.model('0rganization', OrganizationSchema);
module.exports = Organization;
//Comment schema
const mongoose = require('mongoose');
const CommentSchema = mongoose.Schema({
sender: {
type: String,
},
commentBody: {
type: String,
required: false,
},
date: {
type: Date,
default: Date.now
},
})
CommentSchema.statics.getComments= async function () {
try {
const comments = await this.find();
return comments ;
} catch (error) {
throw error;
}
}
const Comment= mongoose.model('Comment', CommentSchema);
module.exports = Comment;
Try to change the comments type to mongoose.Schema.Types.ObjectId:
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment',
},
],
Try to push the new commend _id into the Organization object after its creation, not the whole object:
commentObject.save((err, result) => {
if (err) {
console.log(err);
} else {
Organization.findByIdAndUpdate(
id,
{ $push: { comments: result._id } }, // <- Change this line
{ upsert: true },
(err, organization) => { }
);
...
}
});
If you just updated the schema you will need to make sure all of the comments are following the new form you created, when you save it will attempt to validate them, that is why an updateOne will work but not await save()
I have created a blog schema in mongo which makes reference to the user schema.
However, when I try to save the blog in MongoDB I get the following error: -
CUrrrent post user: new ObjectId("61d28db34c78f60375189033")
User validation failed: passwordHash: Path `passwordHash` is required., name: Path `name` is required., username: Path `username` is required.
I am sending this via JSON
{
"title": "Best Copywriting formulas!",
"author": "Copywriters Inc.",
"url": "https://buffer.com/resources/copywriting-formulas/",
"likes": 420
}
I am unable to decode why I am getting this validation error when I am adding nothing new to the User schema.
Here is my main router code: -
blogRouter.post('/', async (request, response) => {
const blog = new Blog(request.body)
if (blog.author === undefined || blog.title === undefined)
return response.status(400).json({
error: "name or title missing!"
})
//temporary get the first user from the Users db
const userDB = await User.find({});
//Get the first available user in db
const currentUser = userDB[0]._id;
console.log('CUrrrent post user: ', currentUser);
const newBlog = new User({
title: request.body.title,
author: request.body.author,
url: request.body.url,
likes: request.body.likes || 0,
user: currentUser
})
try {
const newEntry = await newBlog.save()
response.status(200).json(newEntry);
} catch (error) {
logger.error(error.message);
}
})
My Blog Schema: -
const blogSchema = new mongoose.Schema({
title: String,
author: String,
url: String,
likes: {
type: Number,
default: 0
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
})
blogSchema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString()
delete returnedObject._id
delete returnedObject.__v
}
})
module.exports = mongoose.model('Blog', blogSchema)
Here is my user Schema: -
var uniqueValidator = require('mongoose-unique-validator');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
minLength: 3,
unique: true
},
name: {
type: String,
required: true
},
passwordHash: {
type: String,
required: true
}
})
userSchema.plugin(uniqueValidator, {message: 'username already taken. {VALUE} not available.'});
userSchema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString()
delete returnedObject._id
delete returnedObject.__v
delete returnedObject.passwordHash
}
})
const User = mongoose.model('User', userSchema);
module.exports = User
You should change your model name when creating a new Blog document:
const newBlog = new Blog({
title: request.body.title,
author: request.body.author,
url: request.body.url,
likes: request.body.likes || 0,
user: currentUser,
});
Also, a good practice would be to check if there are any users in the database before retrieving the first one.
This to avoid possible index out of bounds exceptions:
const userDB = await User.find({});
if (userDB.length > 0) {
const currentUser = userDB[0]._id;
...
I have route and model for User and then another for Loan. I'm trying to reference the user inside the Loan route but I get this error anytime I test on PostMan:
TypeError: Cannot read property '_id' of undefined
at C:\Users\Micho\Documents\GBENGA\BE\src\routes\loans\index.js:38:47
Loan Model code is:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const loanSchema = new Schema({
customerName: {
type: String,
required: true
},
gender: {
type: String
},
address: {
city: String,
state: String,
},
amount: {
type: Number
},
loanTenure: {
type: Number
},
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
loanStatus: {
type: String,
default: "created"
}
}, {
timestamps: true
})
My route is this:
router.post("/", async (req, res) => {
try {
let loan = await new Loan({...req.body});
loan.save();
await User.findByIdAndUpdate(req.user._id, { $push: { loans: loan._id } })
console.log(req.user)
loan = await Loan.findById(loan._id).populate("user");
res.send(loan);
} catch (error) {
console.log(error)
res.status(500).send(error);
}
});
Kindly help. Thanks
I am building an app of Todo Lists, and each list will have an x amount of tasks. The problem is I thought i could do something like this in the taskschema to reference the list the task belongs to:
list: {
type: mongoose.Schema.Types.ObjectId, ref: 'list'
}
Here´s the full code of my models:
const mongoose = require('mongoose')
const listSchema = new mongoose.Schema({
title: {
type: String,
unique: false,
required: [true, 'debes escribir un titulo']
},
createdAt: {
type: Date,
default: Date.now()
}
})
const mongoose = require('mongoose')
const taskSchema = new mongoose.Schema({
name: {
type: String,
unique: false,
required: true
},
doned: {
type: Boolean,
default: false
},
updatedAt: {
type: Date,
default: Date.now()
},
// a task belongs to a list:
list: {
type: mongoose.Schema.Types.ObjectId, ref: 'list'
}
})
But it seems there´s no much help in the web in how to make that work using that approach.
So I took a look at mongoDb documentation and they say they recommend using manual references
So i changed like this:
const mongoose = require('mongoose')
const taskSchema = require('./task')
const listSchema = new mongoose.Schema({
title: {
type: String,
unique: false,
required: [true, 'debes escribir un titulo']
},
createdAt: {
type: Date,
default: Date.now()
},
tasks: [
taskSchema
]
})
But now I´m clueless in how each time I create a task (a POST request to task is made) how I am at the same time will be related to a certain list.
Here´s my api routes for tasks:
routerTasks.post('/task', (req,res, next) => {
Task.create(req.body)
.then(task => res.send(task))
.catch(next)
})
routerTasks.put('/task/:idTask', (req, res) => {
Task.findByIdAndUpdate(req.params.idTask, req.body)
.then(task => res.send({nueva_informacion: task}))
.catch()
})
and for my lists:
routerLists.post('/list', (req, res, next) => {
List.create(req.body).then((list) => {
res.send(list)
}).catch(next)
})
routerLists.put('/list/:id', (req, res) => {
List.findByIdAndUpdate(req.params.id, req.body)
.then(list => res.send({nueva_informacion: list}))
})
My question is... maybe I should create the tasks in a put request of the newly created list? In that case, then the POST request of tasks are useless?
here´s the error that gives me when i try to add a Task and at the same time referencing it to a list:
routerTasks.post('/task/:listId', (req,res, next) => {
List.findOne({_id: req.params.listId}).then((record) => {
record.tasks.push(req.body);
record.save()
})
})
// TypeError: Invalid schema configuration: `model` is not a valid type within the array `tasks`