MongoDB Aggregation Pipelines : Part 2

MongoDB Aggregation Pipelines : Part 2

Writing pipelines to get Watch History in our controllers.

ยท

4 min read

Introduction :

When the client asks for watch history we want to return an array of watchHistory which contains _id of the videos, and some information of the owner ( uploader ) of that video along with a status code and a custom message.

Need of Aggregation Pipelines :

We could easily get the videos in the watchHistory array by performing number of Database queries. But the problem with this approach is that it is less efficient due to large no of individual database queries which increases the latency, less flexible due to individual queries not being capable enough to perform complex operations and less scalable with increasing size of data.

MongoDB aggregation pipelines solves these issues by providing more flexibility and scalability.

Understanding the Working :

  • We first search for the videos in the videos collection of the database which are having the same _id as the elements in the array.

  • Then we search for the owner in the user collection of the database having _id similar to the owner id in the video.

Controller to get User Watch History :

const getWatchHistory = asyncHandler(async (req, res) => {
    const user = await User.aggregate([
        { // Stage 1:
            $match: {
                _id: new mongoose.Types.ObjectId(req.user?._id)// here we use mongoose.types.objectid as req.user.id does not return the _id but the entire string of the id
            }
        },
        { //Stage 2:
            $lookup: {
                from: "videos",
                localField: "watchHistory",
                foreignField: "_id",
                as: "watchHistory",
                pipeline: [
                    {
                        $lookup: {
                            from: "users",
                            localField: "owner",
                            foreignField: "_id",
                            as: "owner",
                            pipeline: [
                                {
                                    $project: {
                                        fullName: 1,
                                        username: 1,
                                        avatar: 1
                                    }
                                }
                            ]
                        }
                    },
                    {
                        $addFields: {
                            owner: {
                                $first: "$owner"
                            }
                        }
                    }
                ]
            }
        }
    ])

    if (!user.length) {
        throw new ApiError(500, "couldn't get user")
    }

    if (!user[0].watchHistory.length) {
        throw new ApiError(400, "No videos in watch history")
    }

    return res
    .status(200)
    .json(new ApiResponse(200, user[0].watchHistory, "Watch history fetched successfully"))
})
  • Stage 1: ( $match )

    This stage filters out the document from the collection of documents ( here different users ) according to the given parameters passed in it and returns it to the next stage.

    In this stage we get all the documents in which the id matches with the id ( after converting the id to into right format as req.user.id is a string ) coming from the req.user ( middleware ).

  • Stage 2: ( $lookup )

    This stage joins two collection in the same database and adds an array field to each document.

    We perform the first lookup to join users collection and videos collection to fetch videos information. We get all the documents from the videos collection which have id similar to the element in the watchHistory array of user and all the documents are stored in the array in the form of objects.

    The second lookup joins the videos collection to the users collection this is for the fetching of owner's information. We get a document from the users collection whose id matches to the owner ( owner id in the user collection as the owner is also an user) in the video document. This document in the form of an array element is stored in the owner array.

    We use $project to have only the required fields in the document of the array ( owner ).

    - - - > So now in owner array we have documents ( object ) in which each document contains the username, fullName and avatar of the owner.

    Now using $addFields we add the first element of owner array (we will only have one object in owner array as there can be only one owner for a video ) in the owner field in the video document.

    - - - > In watchHistory array there are many such video documents ( objects ) consisting of the details of their owner in the form of an object in the owner field.

    - - - > Now the const user is an array which consist only one element i.e. user document which has watchHistory inside which we get our multiple video documents which further has its respective owner document.

After performing proper error handlings we return our response with appropriate status code and the watchHistory of the user along with a custom message.

Testing :

To setup MongoDB Atlas checkout : Hitesh Choudhary's video to setup MongoDB atlas .

If you want the sample data checkout my GitHub Gist AdityaBVerma .

# Notes :

To know more about MongoDB aggregation Pipelines visit MongoDB Aggregation Pipelines .

To learn aggregation pipelines in a fun way do checkout Hitesh Choudhary's playlist .

To know more about the controllers and the utilities and middleware used in it visit my repo on Git hub AdityaBVerma/Backend-with-Js


๐Ÿ‘‹ Hello, I'm Aditya Verma ๐Ÿ˜

โœŒ๏ธ If you liked this article, consider sharing it with others.

๐Ÿ˜Š Feel free to use this article's images and to add comments in case of issues.

๐Ÿฅฐ Thank You for reading.

ย