Skip to content

Using aggregation to implement pagination on Mongoose

Problem

Sometimes when getting object from a Mongo DB we want to get them by parts, this allows us to get only the amount of objects we need or implement pagination on the client

Solution

To implement pagination on Mongoose we could use two approaches, use find and pass the parameters require to obtain the needed objects by pages or using aggregation, I will show you how to implement both

Implementation

The following code uses find to get objects on the database

/**
 * @desc Gets paginated objects from database
 * @param filter the filter to use on the query
 * @param limit the amount of ojects to get
 * @param skip the amount of objects to skip
 * @param sort the field used on sort
 * @param order order of sort
 */
const getObjects = async (filter = {}, limit, skip, sort = "_id", order = -1) => {
        // Options used on query
        const findOptions = {
            sort,
            order,
        };

        // Only use limit if passed as parameter
        if (limit) {
            findOptions.limit = limit;
        }

        // Only use skip if passed as parameter
        if (skip) {
            findOptions.skip = skip;
        }

        // Get objects using options, second parameter is the projected fields, we pass null to get all fields on object
        const objectsFound = await MongooseModel.find(filter, null, findOptions);
        // Get total of objects found, so we know how many pages to display on the client
        const objectFoundCount = await MongooseModel.countDocuments(filter)

        return { data: objectsFound, totalObjects: objectFoundCount };
};

// Example of use of the function above in a express router
router.post("/get-objects", async (req, res) => {
    const { filter, sort, order, limit, skip } = req.body;

    const objectsFound = await getObjects(filter, limit, skip, sort, order);

    res.status(200).send(objectsFound);
});

/**
 * Example getting page 2 of objects with field above 20, we are showing 15 objects per page and sorted by name
 * we skip the first 15 to get page two (amount of items we show * (page - 1) = 15 *(2-1))
 */
const objectsFound = await getObjects({ field: { $gt: 20 } }, 15, 15, "name", 1);

Next we will implement the same functionality but using an aggregation pipeline

/**
 * @desc Gets paginated objects from database using aggregation
 * @param filter the filter to use on the query
 * @param limit the amount of ojects to get
 * @param skip the amount of objects to skip
 * @param sort the field used on sort
 * @param order order of sort
 */
const getObjectsAggregation = async (
        filter = {},
        limit,
        skip,
        sort = "_id",
        order = -1
    ) => {
        // Aggregation pipeline stages
        const findObjectsPipeline = [
            // Filter
            { $match: filter },
            // Sort
            { $sort: { [sort]: order } },
            // Add count of objects found to all objects returned you could use this or objectFoundCount bellow to get total of objects found
            {
                $setWindowFields: { output: { totalDocumentsCount: { $count: {} } } },
            },
        ];

        // Only use skip if passed as parameter
        if (skip) {
            findObjectsPipeline.push({ $skip: skip });
        }

        // Only use limit if passed as parameter
        if (limit) {
            findObjectsPipeline.push({ $limit: limit });
        }

        // Get objects using options, second parameter is the projected fields, we pass null to get all fields on object
        const objectsFound = await MongooseModel.aggregate(findObjectsPipeline);
        // Get total of objects found, so we know how many pages to display on the client
        const objectFoundCount = await MongooseModel.aggregate([
            { $match: filter },
            {
                $count: "quantity",
            },
        ]);

        return { data: objectsFound, totalObjects: objectFoundCount };
};

// Example of use of the function above in a express router
router.post("/get-objects-aggregation", async (req, res) => {
    const { filter, sort, order, limit, skip } = req.body;

    const objectsFound = await getObjectsAggregation(filter, limit, skip, sort, order);

    res.status(200).send(objectsFound);
});

/**
 * Example getting page 2 of objects with field above 20, we are showing 15 objects per page and sorted by name
 * we skip the first 15 to get page two (amount of items we show * (page - 1) = 15 *(2-1))
 */
const objectsFound = await getObjectsAggregation({ field: { $gt: 20 } }, 15, 15, "name", 1);