NodeJS and MongoDB - use aggregate and $lookup together with findById - node.js

I want to make a relation between two collections - a book and author collections.
If i use only get and display all of my books and integrate the data about author by id it works.
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 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: Schema.Types.ObjectId, ref: "Author", required: true }, <-- HERE I NEED DATA ABOUT AUTHOR
});
My express function that works for fetching all data:
router.get("/", async (req, res) => {
try {
let books = await Book.aggregate([
{
$lookup: {
from: "authors",
localField: "authorID",
foreignField: "_id",
as: "author",
},
},
]);
res.status(200).json(books);
} catch (err) {
res.status(404).json({ success: false, msg: "Book is not found" });
}
});
But now I want to display that joined data also when i search for a single book by ID (findById()).
I got an error status if I use a function like this:
router.get("/:bookId", async (req, res) => {
try {
let book= await Book.aggregate([
{
$lookup: {
from: "authors",
localField: "authorID",
foreignField: "_id",
as: "author",
},
},
]);
book= book.findById({ _id: req.params.bookId});
res.status(200).json(book);
} catch (err) {
res.status(404).json({ success: false, msg: "Book is not found" });
}
});
Thank you for your help

use the $match to find only one book for the same query
const mongoose = require('mongoose');
const ObjectId = mongoose.Types.ObjectId();
router.get("/:bookId", async (req, res) => {
try {
let book= await Book.aggregate([
{
$match: { _id : ObjectId("book _id") }
},
{
$lookup: {
from: "authors",
localField: "authorID",
foreignField: "_id",
as: "author",
},
},
]);
book= book.findById({ _id: req.params.bookId});
res.status(200).json(book);
} catch (err) {
res.status(404).json({ success: false, msg: "Book is not found" });
}
});

Related

NodeJs, Mongoose find and populate works but aggregate, match and lookup doesn't

I am having trouble with my aggregation.. I need to return the list of the items which have the same owner's id. Owner's ID I pass as a query param.. I have tried already $match without the $in,
I tried transforming my id to mongoose.Types.ObjectId (with this I event get an error). But yet I accomlpished only empty object as return..
exports.getContainersByOwner = async (req: Request, res: Response) => {
const { id }: any = req.query;
try {
const containers = await Container.aggregate([
{
$lookup: {
from: "containerstypes",
localField: "containerTypeID",
foreignField: "_id",
as: "containerTypes",
},
},
{
$lookup: {
from: "owners",
localField: "ownerId",
foreignField: "_id",
as: "owner",
},
},
{ $unwind: "$containerTypes" },
{ $unwind: "$owner" },
{ $match: { "owner._id": { $in: [id] } } },
]);
res.status(200).json(containers);
} catch (err) {
res.status(404).json({ success: false, msg: "Container not found" });
}
};
If I remove line $match from my code api returns all the items which is OK, but I need to achieve that the returned items have the same id... I need to match the colored id (owner._id)
The Container's model is:
const ContainerSchema = new mongoose.Schema({
ownerId: { type: Schema.Types.ObjectId, ref: "Owners", required: true },
manufacturingNo: { type: String, required: true },
IdentificationNo: { type: String, required: true },
manufacturingDate: { type: Date, required: true },
nextInspectionDate: { type: Date, required: true },
certificateNo: { type: String, required: false },
containerTypeID: {
type: Schema.Types.ObjectId,
ref: "ContainersType",
required: true,
},
});
if I use this approach it works, but object structure is not right, so in frontend there is much more changes to do.. This is not possible at the moment
try {
const containers = await Container.find({
ownerId: id,
})
.populate("containerTypeID")
.populate("ownerId");
res.status(200).json(containers);
} catch (err) {
res.status(404).json({ success: false, msg: "Container not found" });
}
Your $match is logically wrong:
{ $match: { "owner._id": { $in: [id] } } }
Since id, itself is a list of Ids, your $in becomes list of lists ([[]]). Try simply this:
{ $match: { "owner._id": { $in: id } } }

Display data from another collection in MongoDB

I have two separate collection in MongoDB 1-> Post, 2-> Comment.
Post-schema:
const postSchema = new mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
media: {
type: [mongoose.Schema.Types.Mixed],
trim: true,
required: true,
},
text: String,
mentions: {
type: Array,
default: [],
},
hashTags: {
type: ["String"],
},
likes: {
type: Array,
default: [],
},
postStatus: {
type: "String",
default: "public",
enum: ["public", "private"],
},
deletedAt: {
type: "Date",
default: null,
},
},
{ timestamps: true }
);
comment schema:
const commentSchema = new mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
postId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Post",
required: true,
},
commentText: String,
},
{ timestamps: true }
);
Now I want to display all the comments of requested post. Post table does not have anything that links to Comment that is why I can not use populate(). but Comment table have postId to connect.
Here's what I have tried:
exports.getPostById = async (req, res) => {
try {
let post = await Post.findById(req.params.id);
if (!post) return res.status(404).json({ message: "No Post found" });
let comment = await Comment.find({ postId: { $in: {post: req.params.id} } });//Wrong query
return res.status(200).send(post);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
Use lookup in an aggregation query.
please refer to this link.
https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/
example:
db.post.aggregate([{
$lookup: {
from: ‘comment’,
localField: ‘user_id’,
foreignField: ‘user_id’,
as: ‘whatever_name_you_want’
}}]);
Try this:
Transform the id string from params to a ObjectId:
let postObjectId = mongoose.Types.ObjectId(req.params.id);
Query using the variable defined above:
let comments = await Comment.find({ postId: postObjectId});

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"
}
}
]);

Mongodb 2 level lookup

I'm a nodejs beginner and cannot debug my problem
I have 3 models.
Cargo model
const mongoose = require("mongoose");
const cargoSchema = mongoose.Schema({
description: { type: String, required: true },
cargoType: { type: String, enum:['irt', 'prt', 'adr', 'pr', 'npr', 'it', 'pt', 'izt', 'hsr', 'tpd', 'zt', 'vt', 'tpv', 'st', 'pv', 'other']},
price: { type: String, required: true },
date: { type: Date },
creator: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }
});
module.exports = mongoose.model("Cargo", cargoSchema);
Signups model
const mongoose = require("mongoose");
const signupSchema = mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
cargoId: { type: mongoose.Schema.Types.ObjectId, ref: "Cargo", required: true },
truckId: { type: mongoose.Schema.Types.ObjectId, ref: "Truck", required: true },
approved: { type: Boolean ,required: true },
finished: { type: Boolean ,required: true },
});
module.exports = mongoose.model("Signup", signupSchema);
and User model
const mongoose = require("mongoose");
const uniqueValidator = require("mongoose-unique-validator");
const userSchema = mongoose.Schema({
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
isEmailVerified: { type: Boolean },
registrationStep: { type: Number, enum: [0,1,2,3]},
regDate: { type: Date },
companyName: { type: String },
oib: { type: String },
telephone: { type: String },
address: { type: String },
city: { type: String },
countryCode: { type: String },
postalCode: { type: String },
userType: { type: String, enum:['firms','drivers','both']},
approved: { type: Boolean },
isAdmin: { type: Boolean }
});
userSchema.plugin(uniqueValidator);
module.exports = mongoose.model("User", userSchema);
I want to get data from Cargos for a specific user and also get data from Cargo signups and specific user for that signup
This is my controller
exports.getCargosByUserId = (req, res, next) => {
const ObjectId = mongoose.Types.ObjectId;
const cargoQuery = Cargo.aggregate([
{ $match: {creator: ObjectId(req.params.id)}},
{
$lookup: {
from: Load.collection.name,
localField: "_id",
foreignField: "cargoId",
as: "loads"
}
},
{
$lookup: {
from: UnLoad.collection.name,
localField: "_id",
foreignField: "cargoId",
as: "unLoads"
}
},
{
$lookup: {
from: Signup.collection.name,
localField: "_id",
foreignField: "cargoId",
pipeline: [{
$lookup: {
from: User.collection.name,
localField: "_id",
foreignField: "userId",
as: "signupUser"
}
}],
as: "signUps"
}
}
]);
cargoQuery
.then(documents => {
fetchedCargos = documents;
res.status(200).json({
message: "Cargo fetched successfully!",
cargos: fetchedCargos,
});
})
.catch(error => {
res.status(500).json({
message: "Fetching cargo failed!"
});
});
};
Nodejs doesn't print any error, so it's hard to debug, but somewhere I have made a mistake. I'm recieving cath block "Fetching cargo failed". Thank you for your help!
I cant comment you but a good help would be to print the error like this so you know more info about it:
.catch(error => {console.log(error)})

Mongoose $lookup object returns empty array

I have tried other similar kind of questions available but nothing seems to work for me.
I have two collections:
leads:
const mongoose = require("mongoose");
const id = mongoose.Schema.Types.ObjectId;
const leadsSchema = mongoose.Schema(
{
_id: id,
userId: { type: id, ref: "User", required: true },
leadName: String,
leads: [
{
_id: id,
name: String,
status: { type: String, required: false, default: "New" },
leadActivity: { type: String, required: false, default: "No Campaign Set" },
headline: { type: String, required: false },
location: { type: String, required: false },
leadType: { type: id, ref: "LeadsCategory", required: true },
}
],
campaignAssociated: {type: id, ref: "campaign"},
},
{
timestamps: true
}
);
module.exports = mongoose.model("lead", leadsSchema);
leadCategory
const mongoose = require("mongoose");
const leadsCategorySchema = mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
name: {
type: String,
required: false,
},
leadsData: [{ type: Array, ref: "lead" }],
},
{ timestamps: true }
);
module.exports = mongoose.model("LeadsCategory", leadsCategorySchema);
I am trying to reference/populate the name of the lead from leadscategory schema into the leads
exports.get_single_lead_info = (req, res) => {
const { userId } = req.user;
const { leadid } = req.body;
let idToSearch = mongoose.Types.ObjectId(leadid);
Lead.aggregate([
{
$lookup: {from: 'leadscategories', localField: 'leadType', foreignField: 'name', as: 'type as'}
},
{
$match: {
userId: mongoose.Types.ObjectId(userId),
},
},
{
$unwind: "$leads",
},
{
$match: {
"leads._id": idToSearch,
},
},
])
.exec(function (err, result) {
if (err) {
return res.status(400).json({ message: "Unable to fetch data", err });
}
if (!result.length) {
res.status(404).json("No result found");
} else {
res.status(200).json({ message: "Lead info found", result });
}
});
};
But it outputs me the lookup result as an empty array everytime:
{
"message": "Lead info found",
"result": [
{
"_id": "5ece11cbac50c434dc4b7f2c",
"leadName": "python",
"leads": {
"status": "New",
"leadActivity": "Campaign Set",
"name": "Hailey",
"headline": "Machine Learning | Python",
"location": "New Delhi Area, India",
"_id": "5ece11cbac50c434dc4b7f29",
"leadType": "5ebce0f81947df2fd4eb1060"
},
"userId": "5eba83d37d4f5533581a7d58",
"createdAt": "2020-05-27T07:07:55.231Z",
"updatedAt": "2020-05-27T10:47:42.098Z",
"__v": 0,
"type as": [] //<--- Need lead type name associated inside this
}
]
}
Input: "leadid": "5ece11cbac50c434dc4b7f29"
Any help appreciated.
[
{
$match: {
userId: mongoose.Types.ObjectId(userId),
},
},
{
$unwind: "$leads",
},
{
$match: {
'leads._id': idToSearch,
},
},
{
$lookup: {
from: 'leadscategories',
localField: 'leads.leadType',
foreignField: '_id',
as: 'type as'
}
},
]

Resources