How to Implement Pagination in Mongoose

Updated: December 30, 2023 By: Guest Contributor Post a comment

Pagination is a vital feature when dealing with large datasets in a MongoDB database using Mongoose ORM in a Node.js environment. Implementing efficient pagination can significantly enhance performance and usability. Below are three solutions for implementing pagination in Mongoose, demonstrated using up-to-date JavaScript syntax and patterns.

Offset/Limit Pagination

This traditional approach involves skipping a set number of records (offset) and then limiting the number of records returned:

  • Decide the page size and calculate the offset based on the page number.
  • Use the .skip() and .limit() methods in Mongoose to paginate the results.

Code example:

const mongoose = require('mongoose');
const { Schema } = mongoose;

const PostSchema = new Schema({ /* schema definition */ });
const Post = mongoose.model('Post', PostSchema);

async function fetchPosts(page, pageSize) {
  const offset = (page - 1) * pageSize;
  return await Post.find().skip(offset).limit(pageSize);
}

Pros: Simple to implement and understand.

Cons: Performance issues with large datasets due to high offset values.

Cursor Based Pagination

Using a unique identifier or cursor to fetch records after the cursor:

  • Perform initial query to retrieve the first set of results along with a cursor (usually the _id).
  • For consecutive pages, query the next set of results by finding records where the cursor is greater than the last cursor provided (for ascending order).

Code example:

const fetchPostsAfterCursor = async (cursor, pageSize) => {
  return cursor
    ? await Post.find({ _id: { $gt: cursor } }).limit(pageSize)
    : await Post.find().limit(pageSize);
};

Pros: Better performance especially with larger datasets and is the preferred approach for infinite scrolling implementations.

Cons: Cannot directly jump to a specific page, more complex to implement than offset/limit.

Aggregation Framework Pagination

Utilizing the MongoDB aggregation framework for more complex queries. Here’re the steps to follow:

  • Build an aggregation pipeline that includes the pagination logic.
  • Perform operations such as matching (filtering), sorting, skipping, and limiting.

Example:

const fetchPostsUsingAggregation = async (page, pageSize) => {
  const skips = pageSize * (page - 1);
  return Post.aggregate([
    { $match: {}},
    { $sort: { createdAt: -1 }},
    { $skip: skips },
    { $limit: pageSize }
  ]); 
};

Pros: Extremely flexible, allowing complex queries and transformations within the pagination.

Cons: More intricate and involves a steeper learning curve to utilize correctly compared to other methods.

Final Words

Pagination in Mongoose allows you to handle large datasets effectively without overwhelming your server or your users. Each method has its specific use case; the right choice depends on your dataset size, the required user experience, and the complexity of your queries. Offset/limit is straightforward but slows down with large datasets, cursor-based is timely for large datasets with smoother user experience needs, and the aggregation framework offers the most flexibility at the cost of complexity.