dynamic references by sending it from the route - node.js

I have been looking at the documentation and some other questions made, but I have not been able to do it, I need to consult another collection for an objectId, but these collections come dynamically in the routes.
The collections come req, as they are from many different clients
https://mongoosejs.com/docs/populate.html
//route
// example collection dynamic ssdf2451_users, ssdf2451_campus, ssdf2451_programs
router.get('/school/prueba/tipos/', async (req, res, next) => {
let users
let school
let prueba
try {
const Users = model(`ssdf2451_users`, UserSchema)
console.log(Users)
await Users.findOne({ _id: '5ef56f70d19aea6e70c82a50' })
.populate('schoolId')
.exec(function (err, usersDocuments) {
console.log(usersDocuments, err)
// handle err
prueba = usersDocuments
res.status(200).json(prueba)
})
} catch (err) {
next(err)
}
})
// Schema
import { Schema, model } from 'mongoose'
const UserSchema = new Schema({
state: {
type: Boolean,
default: false,
required: false
},
accountNumber: {
type: Number,
required: false
},
schoolId: {
type: Schema.Types.ObjectID,
required: true,
ref: 'schools'
},
campusId: {
type: Schema.Types.ObjectID,
required: true,
ref: dynamic
},
programsId: {
type: [Schema.Types.ObjectID],
required: false,
ref: dynamic
},
})
const User = model('users', UserSchema)
export { User, UserSchema }

Related

Moongoose populate method returning an empty array

I am trying to fetch the blogs that a user has posted using mongoose populate method but not able to do so but when I try to look for the user who posted a blog I am able to do so. Tell me what I am doing wrong.
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const userSchema = new Schema({
username: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
blogPosts: [{ type: mongoose.Types.ObjectId, ref: "Blogs", required: true }],
});
const Users = mongoose.model("Users", userSchema);
module.exports = Users;
This is my user model
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const blogSchema = new Schema({
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
image: {
type: String,
required: true,
},
user: {
type: mongoose.Types.ObjectId,
ref: "Users",
required: true,
},
});
const blogs = mongoose.model("Blogs", blogSchema);
module.exports = { blogs, blogSchema };
this is my blogs model
router.get("/users/:id", async (req, res) => {
let userBlogs;
try {
userBlogs = await users.find({ _id: req.params.id }).populate("blogPosts");
if (!userBlogs) {
return res.status(404).json({ msg: "No user found" });
}
return res.status(200).json(userBlogs);
} catch (err) {
return res.status(500).json({ err });
}
});
this is the route that I use for fetching all the blogs that a user has posted.This is just sending me back an empty posts array.
router.post("/newBlog/:id", async (req, res) => {
let blogAuthor;
try {
blogAuthor = await users.findOne({ _id: req.params.id });
} catch (err) {
return res.status(404).json({ message: "user not found" });
}
const newBlog = blogs({
title: req.body.title,
description: req.body.description,
image: req.body.imageUrl,
user: blogAuthor._id,
});
try {
await blogAuthor.blogPosts.push(newBlog);
await newBlog.save();
return res.status(200).json(blogAuthor.blogPosts);
} catch (err) {
return res.status(500).json(err);
}
});
the above is the route that I use to add new blog to the db.
router.get("/allBlogs", async (req, res) => {
let allBlogs;
try {
allBlogs = await blogs.find({}).populate("user");
} catch (err) {
return res.status(404).json(err);
}
res.status(200).json(allBlogs);
});
this is the route that sends all the blogs posted by all the users.
Interesting thing is that when I try to populate the user of a blogpost that is working exactly as expected it is populating the user of the blog from the users model however when I try to do the reverse that's not working. It is not populating all the blogs that a user has posted.

MongoDB relation between two collections by ID with the Express

I am facing a problem while making a relation between two collections (I am using MEAN stack)
I have two collections: Books and Authors
In frontend I want to make a CRUD menu, where I add a new book in the table and then from there i insert a few data about book and then I choose author from the dropdown menu (fetchin data from Authors collection)
So at the end my Book collection needs to have a few data about the book and then inside the object i need an array of data about those author.
Book schema:
const BookSchema = new mongoose.Schema({
owner: { type: String, required: true },
pagesNo: { type: String, required: true },
releaseDate: { type: String, required: true },
country: { type: String, required: true },
authorID: { type: String, required: true }, <-- HERE I NEED DATA ABOUT AUTHOR
});
Author schema:
const AuthorSchema = new mongoose.Schema({
name: { type: String, required: true },
surname: { type: String, required: true },
dateOfBirth: { type: String, required: true },
countryOfBirth: { type: String, required: true },
});
Book route: book.ts
router.get("/", async (req, res) => {
try {
const books= await Book.find();
let Author = await Author.find({
books: { $elemMatch: { _id: books.bookID } },
});
res.status(200).json(books);
} catch (err) {
res.status(404).json({ success: false, msg: "Booknot found" });
}
});
The problem is somewhere inside the find() function.. Is it even a good practice? I want that it can handle a lot of data.
Thanks to everyone!
Greetings.
Your Book schema would be like this:
const MongooseSchema = new mongoose.Schema({
owner: {
type: String,
required: true,
},
pagesNo: {
type: String,
required: true,
},
releaseDate: {
type: String,
required: true,
},
country: {
type: String,
required: true,
},
authorId: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
});
And your Author Schema would remain the same (in order to link both schemas).
Your route would be like this (if you want to search all books along with their author names):
router.get('/', async (req, res) => {
try {
const books = await Book.find().populate('authorId');
res.status(200).json(books);
} catch (err) {
res.status(404).json({ success: false, msg: 'Booknot found' });
}
});
And in case you want to search for books with a specific author id then your route would be like this:
router.get('/', async (req, res) => {
try {
const books = await Book.find({ authorId }).populate('authorId');
res.status(200).json(books);
} catch (err) {
res.status(404).json({ success: false, msg: 'Booknot found' });
}
});
AuthorID should be type ObjectId, not string.
To join data from other table, you have to use an aggregate with a lookup.
let author = await Author.aggregate([
{
$lookup:
{
from: "books",
localField: "_id",
foreignField: "authorID",
as: "books"
}
}
]);

Unable to populate nested schema reference in Mongoose

I have multiple Schemas and I'm trying to query the data using populate. I'm using mongoose(5.9.7) and express js.
First Schema - ProfileSchema
const ProfileSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "User"
},
jobs: [
{
type: Schema.Types.ObjectId,
ref: "Job"
}
],
resumes: [
{
type: Schema.Types.ObjectId,
ref: "Resume"
}
],
name: {
type: String,
required: true
},
});
module.exports = Profile = mongoose.model("Profile", ProfileSchema);
Second Schema - Resume Schema
const ResumeSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "User"
},
fileLink: { type: String, required: true },
fileName: { type: String, required: true },
description: { type: String, required: true }
});
module.exports = Resume = mongoose.model("Resume", ResumeSchema);
Route
router.get(
"/",
passport.authenticate("jwt", { session: false }),
async (req, res) => {
try {
const profile = await Profile.findOne({
user: req.user.id
})
.populate("resumes")
.exec();
res.json(profile);
} catch (error) {
return res.status(400).json(error);
}
}
);
This returns an empty array for both resume and jobs. I have tried different variations of populate but it doesn't work. Tried deeppopulate as well. Though User gets populated fine.
EDIT: Adding resume upload route
Route
router.post(
"/upload",
passport.authenticate("jwt", { session: false }),
upload.single("resume"),
async (req, res) => {
try {
// upload to s3 code here
if (data) {
const resumeData = {
description: req.body.description,
fileName: params.Key,
fileLink: data.Location,
user: req.user.id
};
const resume = new Resume(resumeData);
const fileSavedToSchema = await resume.save();
return res.json(fileSavedToSchema);
}
} catch (error) {
console.log(error);
}
}
);
I'm able to view the resumes using a different route.

how to fetch user details from populated table in nodejs?

I have two tables in my MongoDB cluster i.e Post and User. Actually I want to retrieve the username and userID along with a post from the post table when a user posts something.
my post controller
router.getPostsList = async (req, res) => {
try {
const posts = await Post.list();
return res.status(200).json(posts);
} catch (err) {
return res.status(400).json(err);
}
};
MongoDB Post collection Image
PostSchema
const PostSchema = new mongoose.Schema(
{
text: {
type: String,
trim: true
},
slug: {
type: String,
trim: true,
lowercase: true
},
user: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}
]
},
{ timestamps: true }
);

mongoose: create new element in X collection, update another in Y collection

I am trying to develop a CRUD app for users to store, add, delete and update recipes. It's built on MEVN stack. As I need to show the user, which recipes they have created, I am trying to create a recipe based on this model:
const RecipeSchema = new Schema({
title: {
type: String,
required: [true, 'Title of the recipe is required'],
},
category: {
type: Array,
required: [true, 'Category is required'],
},
description: {
type: String,
required: [true, 'Description is required'],
},
imgUrl: {
type: String,
required: [true, 'Image is required'],
},
ingredients: {
type: Array,
required: [true, 'Ingredients are required'],
},
timeOfPreparation: {
type: String,
required: true,
},
preparation: {
type: String,
required: true,
},
sourceName: {
type: String,
required: true,
},
sourceUrl: {
type: String,
required: true,
},
author: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
});
const Recipe = mongoose.model('Recipe', RecipeSchema);
module.exports = Recipe;
And at the same time update User model, based on this:
const UserSchema = Schema({
googleId: String,
name: String,
favorites: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' }],
authoredRecipes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' }],
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
In the controller, I have this method (as per #Stock Overflaw comment):
exports.create_new_recipe = (req, res, next) => {
Recipe.create(req.body)
.then(recipe => {
User.update(
{ _id: req.body.author },
{
$push: { authoredRecipes: recipe.id },
}
);
res.send(res.status);
})
.catch(error => {
res.status(500).json({ error });
});
};
This method is called when I go to /create endpoint. However, even though I do get all the correct ids (req.body.author and recipe.id), I cannot get this to work. In my mLab recipe collection the recipe is displayed correctly (all data that I have inserted with authorId), however in the User collection, the array of authoredRecipes stays empty.
How can I get mongoose to both create an object in one collection as well as update another object based on their ids?
The documentation for findByIdAndUpdate requires the _id field as its value, not an object:
User.findByIdAndUpdate(req.body.author, {
$push: { authoredRecipes: recipe.id }
});
// equivalent to the more general method:
User.findOneAndUpdate({ _id: req.body.author }, {
$push: { authoredRecipes: recipe.id }
});
// and if you don't need the modified document in your callback, this should be faster:
// EDIT: this is advised against (we should use a user object, not the collection)
User.update({ _id: req.body.author }, { // or updateOne
$push: { authoredRecipes: recipe.id }
});
Edit: a working, minimal example
Mind {new: true} maybe? Depending on how you test whether it works...
const mongoose = require('mongoose');
const fs = require('fs');
const userIdFile = './tmp.txt'; // just for this test
mongoose.connect('mongodb://localhost/meuh', {
useNewUrlParser: true, // removes a deprecation warning
useFindAndModify: false // removes another deprecation warning
});
// make schemas/models
const RecipeSchema = mongoose.Schema({
title: { type: mongoose.Schema.Types.String }
});
const UserSchema = mongoose.Schema({
name: { type: mongoose.Schema.Types.String },
data: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' }]
});
const RecipeModel = mongoose.model('Recipe', RecipeSchema);
const UserModel = mongoose.model('User', UserSchema);
// user precreation
// UserModel.create({
// name: 'me, myself and I'
// }).then((user) => {
// fs.writeFile(userIdFile, user.id, console.log.bind(null, 'error writing file:'));
// mongoose.connection.close();
// });
// return;
// fetch user
const userId = fs.readFileSync(userIdFile);
let pushedRecipeId; // to test everything went smooth
RecipeModel.create({
title: 'pasta solo'
}).then((recipe) => {
console.log('created recipe:', recipe);
pushedRecipeId = recipe.id;
return UserModel.findOneAndUpdate(
{ _id: userId },
{ $push: { data: recipe.id } },
{ new: true } // forces callback to be passed a fresh object
);
}).then((user) => {
console.log('updated user:', user);
console.log('izok:', !!~user.data.indexOf(pushedRecipeId));
mongoose.connection.close();
}).catch((err) => {
console.log('error', err);
mongoose.connection.close();
})
Example output I got:
# creating user (uncommented this part)
ubuntu#ubuntu-VirtualBox:~/web/test$ node .
error writing file: null
# calling for $push (re-commented user creation)
ubuntu#ubuntu-VirtualBox:~/web/test$ node .
created recipe: { _id: 5c72be7032bd2f1acad37c95, title: 'pasta solo', __v: 0 }
updated user: { data: [ 5c72be7032bd2f1acad37c95 ],
_id: 5c72be6a8143fd1aa9416d85,
name: 'me, myself and I',
__v: 0 }
izok: true
# again $push
ubuntu#ubuntu-VirtualBox:~/web/test$ node .
created recipe: { _id: 5c72c020c2ac7a1b8c65fa36, title: 'pasta solo', __v: 0 }
updated user: { data: [ 5c72be7032bd2f1acad37c95, 5c72c020c2ac7a1b8c65fa36 ],
_id: 5c72be6a8143fd1aa9416d85,
name: 'me, myself and I',
__v: 0 }
izok: true
# and again
ubuntu#ubuntu-VirtualBox:~/web/test$ node .
created recipe: { _id: 5c72c023bf62331b97ef096b, title: 'pasta solo', __v: 0 }
updated user: { data:
[ 5c72be7032bd2f1acad37c95,
5c72c020c2ac7a1b8c65fa36,
5c72c023bf62331b97ef096b ],
_id: 5c72be6a8143fd1aa9416d85,
name: 'me, myself and I',
__v: 0 }
izok: true
# end
ubuntu#ubuntu-VirtualBox:~/web/test$
I don't see what's wrong in your code, but at least you have something to compare with... hope this helps!

Resources