Reputation: 866
I have User
and Training
model. Both hold likes
property set to be an array. The idea is that I would like to create some kind of "add-to-favorite" functionality.
User model
likes: [
{
type: mongoose.Schema.ObjectId,
ref: 'Training',
},
],
Training model
likes: [
{
type: mongoose.Schema.ObjectId,
ref: 'Training',
},
],
In my controller I created a function which is responsible for populating user id in Training and vice versa.
exports.favoriteTraining = catchAsync(async (req, res, next) => {
const user = await User.findById(req.user.id);
const training = await Training.findById(req.params.id);
const trainingLikes = training.likes.filter((like) => {
return like.toString() === req.user.id;
});
if (trainingLikes.length > 0) {
return next(new AppError('Training already liked', 400));
}
user.likes.unshift(req.params.id);
training.likes.unshift(req.user.id);
await user.save({ validateBeforeSave: false });
await training.save({ validateBeforeSave: false });
res.status(201).json({
status: 'success',
data: {
user,
training,
},
});
});
Current solution works, but I was wondering if there is any other way where I do not need to query both databases separately.
Upvotes: 1
Views: 1901
Reputation: 17858
You can simplify your user schema, and the logic to favorite a training by removing the likes from user model.
Here are the steps:
1-) Remove the likes from user model
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
name: String,
});
module.exports = mongoose.model("User", UserSchema);
2-) Now we only need to modify the likes array in the training model.
exports.favoriteTraining = catchAsync(async (req, res, next) => {
const loggedUserId = req.user.id;
let training = await Training.findById(req.params.id);
const alreadyLiked = training.likes.find((like) => like.toString() === loggedUserId) !== undefined;
if (alreadyLiked) return next(new AppError("Training already liked", 400));
training.likes.push(loggedUserId);
training = await training.save({ validateBeforeSave: false });
res.status(201).json({
status: "success",
data: {
training,
},
});
});
As you see we only made 2 db operations here (it was 4 ). Also I advise to use push
instead of unshift
, unshift modifies the index of all items in array, push only add the new item to the end.
3-) Since we removed the likes from user, we need to find a way to reference the trainings from user.
Let's say we want to find a user by id and get the trainings he/she favorited. We can use mongodb $lookup aggregation to achieve this. (We could also use virtual populate feature of mongoose, but lookup is better.)
exports.getUserAndFavoritedTrainings = catchAsync(async (req, res, next) => {
const loggedUserId = req.user.id;
const result = await User.aggregate([
{
$match: {
_id: mongoose.Types.ObjectId(loggedUserId),
},
},
{
$lookup: {
from: "trainings", //must be physcial name of the collection
localField: "_id",
foreignField: "likes",
as: "favorites",
},
},
{
$project: {
__v: 0,
"favorites.likes": 0,
"favorites.__v": 0,
},
},
]);
if (result.length > 0) {
return res.send(result[0]);
} else {
return next(new AppError("User not found", 400));
}
});
TEST:
Let's say we have these 2 users:
{
"_id" : ObjectId("5ea7fb904c166d2cc42fd862"),
"name" : "Krzysztof"
},
{
"_id" : ObjectId("5ea808988c6f2207c8289191"),
"name" : "SuleymanSah"
}
And these two trainings:
{
"_id" : ObjectId("5ea7fbc34c166d2cc42fd863"),
"likes" : [
ObjectId("5ea7fb904c166d2cc42fd862"), // Swimming favorited by Krzysztof
ObjectId("5ea808988c6f2207c8289191") // Swimming favorited by SuleymanSah
],
"name" : "Swimming Training",
},
{
"_id" : ObjectId("5ea8090d6191c60a00fe9d87"),
"likes" : [
ObjectId("5ea7fb904c166d2cc42fd862") // Running favorited by Krzysztof
],
"name" : "Running Training"
}
If the logged in user is Krzysztof, the result will be like this:
{
"_id": "5ea7fb904c166d2cc42fd862",
"name": "Krzysztof",
"favorites": [
{
"_id": "5ea7fbc34c166d2cc42fd863",
"name": "Swimming Training"
},
{
"_id": "5ea8090d6191c60a00fe9d87",
"name": "Running Training"
}
]
}
You can play with this aggregation in this playground
Upvotes: 3