MongooseJS: Cursor-Based Pagination Examples

Updated: February 22, 2024 By: Guest Contributor Post a comment


When building applications with large datasets, effective pagination strategies become crucial for performance and user experience. MongooseJS, a popular MongoDB object modeling tool for Node.js, supports cursor-based pagination, a method well-suited for scaling and real-time data updates. This tutorial explores how to implement cursor-based pagination with MongooseJS through practical examples.

Understanding Cursor-Based Pagination

Unlike traditional offset-based pagination, which skips a fixed number of documents, cursor-based pagination uses a pointer (“cursor”) to traverse through documents. This approach offers several advantages, including constant query performance regardless of page number and a stable view of data even when new records are added or existing ones are modified.

Setting Up the Environment

To follow along, ensure you have Node.js and MongoDB installed. Initialize a new Node.js project and install Mongoose:

$ npm init -y
$ npm install mongoose

Next, connect Mongoose to your MongoDB instance:

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/yourDB', {
 useNewUrlParser: true,
 useUnifiedTopology: true

Model Definition

Define a simple model to work with. For example, a blog post schema:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const postSchema = new Schema({
 title: { type: String, required: true },
 body: { type: String, required: true },
 createdAt: { type: Date, default: }

const Post = mongoose.model('Post', postSchema);

Basic Cursor-Based Pagination

To implement cursor-based pagination, you start by querying a set amount of documents, using the _id or another field as your cursor. Here’s a function to fetch posts:

async function fetchPosts(cursor, limit = 10) {
 let query = {};
 if (cursor) {
 query._id = { $gt: cursor };
 return await Post.find(query).limit(limit);

This function retrieves a fixed number of documents (limit) that come after the specified cursor.

Improving Performance with Indexes

To ensure queries run efficiently, especially on large datasets, it’s crucial to add indexes on the fields used for pagination (e.g., _id or createdAt):

postSchema.index({ createdAt: 1 });

This index supports efficient retrieval by createdAt order.

Advanced Cursor-Based Pagination

An advanced strategy involves using timestamps for cursor pagination. Assuming posts have a `createdAt` field, you could paginate based on document creation time:

async function fetchPostsByTime(cursor, limit = 10) {
 let query = {};
 if (cursor) {
 query.createdAt = { $gt: cursor };
 return await Post.find(query).sort({ createdAt: 1 }).limit(limit);

This method offers a chronological view, ideal for real-time data feeds.

Tips for Effective Cursor-Based Pagination

  • Choose appropriate cursor fields: While _id is a common and easy choice, other fields like timestamps can be more suitable depending on the use case.
  • Communicate with frontend clearly: Ensure the frontend is aware of how cursors are passed and handled. Cursors can be opaque strings or specific field values.
  • Implement client-side caching: For frequently accessed pages, caching the results on the client side can significantly improve the user experience.


Cursor-based pagination in MongooseJS improves user experience and application performance, especially for large and continuously updating datasets. By following the outlined examples and best practices, developers can effectively implement this pagination strategy in their applications.