@dakohhh/mongoose-paginator
v1.0.6
Published
Mongoose-Class-Paginator is a lightweight, flexible, and TypeScript-friendly pagination utility for Mongoose models. It provides a class-based approach to handling database pagination with support for filtering, sorting, population, projection, and metada
Maintainers
Readme
@dakohhh/mongoose-paginator
A lightweight, flexible, and TypeScript-friendly pagination utility for Mongoose models. This package provides a class-based approach to handling database pagination with support for filtering, sorting, population, projection, and metadata customization — all without relying on heavy plugins.
Features
- 🚀 Lightweight & Performant: Minimal overhead with optimized query execution
- 🔧 Fully Configurable: Customize filters, sorting, projections, and more
- 📊 Rich Metadata: Comprehensive pagination metadata including total count, page info, and navigation
- 🧩 Fluent API: Chainable methods for a clean and intuitive interface
- 📝 TypeScript Support: Full type safety with generic typing
- 🔄 Document/JSON Flexibility: Support for both Mongoose documents and lean objects
- 🔍 Advanced Querying: Compatible with all Mongoose query operators
- 🧪 Zero Dependencies: No external dependencies beyond Mongoose
Installation
npm install @dakohhh/mongoose-paginatorOr with yarn:
yarn add @dakohhh/mongoose-paginatorUsage
⚠️ Deprecation Notice:
The
Paginatorclass has been deprecated and will be removed in a future release. Please use thePageNumberPaginatorclass instead.PageNumberPaginatoris functionally identical toPaginatorand is now the recommended way to perform page-number-based pagination.import { PageNumberPaginator } from '@dakohhh/mongoose-paginator'; // Usage is identical to the old Paginator class.
Basic Example
import { Paginator } from '@dakohhh/mongoose-paginator';
import { model, Schema, Document } from 'mongoose';
// Define your Mongoose model
interface IUser extends Document {
name: string;
email: string;
age: number;
createdAt: Date;
}
const UserModel = model<IUser>('User', userSchema);
// Create a paginator instance
const paginator = new Paginator<IUser>(UserModel);
// Execute pagination
const result = await paginator.paginate();
console.log(result);
// Output: { data: [...], meta: { total, lastPage, currentPage, perPage, prev, next } }
// You can also destructure the results for cleaner code
const { data: users, meta: paginationInfo } = await paginator.paginate();
console.log(users); // Array of user documents
console.log(paginationInfo); // Pagination metadataAdvanced Usage
Page Number Pagination (Recommended)
import { PageNumberPaginator } from '@dakohhh/mongoose-paginator';
const page = 1
const limit = 10
const paginator = new PageNumberPaginator<IUser>(UserModel, page, limit, {
filter: { age: { $gte: 18 } },
sort: { createdAt: -1 },
projection: ['name', 'email', '-_id'],
populate: [{ path: 'posts', select: 'title' }],
lean: true
});
const result = await paginator.paginate();
console.log(result);
// {
// data: [...],
// meta: { total, lastPage, currentPage, perPage, prev, next }
// }Offset Pagination
Offset-based pagination is useful for traditional skip-limit pagination scenarios.
import { OffsetPaginator } from '@dakohhh/mongoose-paginator';
const offset = 0; // Start offset
const limit = 10; // Items per page
const paginator = new OffsetPaginator<IUser>(UserModel, offset, limit, {
filter: { age: { $gte: 18 } },
sort: { createdAt: -1 },
projection: ['name', 'email', '-_id'],
populate: [{ path: 'posts', select: 'title' }],
lean: true
});
const result = await paginator.paginate();
console.log(result);
// {
// data: [...],
// meta: { total, lastPage, currentPage, perPage, prev, next }
// }Cursor Pagination
Cursor-based pagination is ideal for real-time feeds or infinite scrolling. Use the nextCursor from the previous result to fetch the next page.
import { CursorPaginator } from '@dakohhh/mongoose-paginator';
let cursor: string | null = null; // BSON ObjectId
const limit = 10;
const paginator = new CursorPaginator<IUser>(UserModel, cursor, limit, {
filter: { age: { $gte: 18 } },
projection: ['name', 'email', '-_id'],
populate: [{ path: 'posts', select: 'title' }],
lean: true
});
const result = await paginator.paginate();
console.log(result);
// {
// data: [...],
// meta: { nextCursor }
// }
// To fetch the next page:
cursor = result.meta.nextCursor;
const nextPage = await new CursorPaginator<IUser>(UserModel, cursor, limit).paginate();Result Customization
One of the key advantages of this paginator is the ability to easily customize how you access your results through destructuring:
// Standard result object
const result = await paginator.paginate();
// Customize variable names through destructuring
const { data: users, meta: paginationInfo } = await paginator.paginate();
// Access your data with your preferred variable names
console.log(users); // Your paginated documents with your custom name
console.log(paginationInfo.currentPage); // Access pagination metadata with your custom name
// Use in a response object
return {
success: true,
users, // Your renamed data array
pagination: paginationInfo // Your renamed metadata
};API Reference
Paginator<T>
The main class for pagination operations.
Constructor
constructor(
model: Model<T>,
page: number = 1,
limit: number = 10,
args: IPaginateArgs<T> = { filter: {}, sort: { createdAt: -1 }, lean: true }
)Methods
paginate(): Executes the pagination query and returns results with metadatasetPage(page: number): Sets the current page numbersetLimit(limit: number): Sets the number of items per pagesetArgs(args: IPaginateArgs<T>): Sets the query arguments
Interfaces
IPaginateArgs<T>
interface IPaginateArgs<T> {
filter?: FilterQuery<T>;
sort?: Record<string, SortOrder>;
projection?: string | string[] | Record<string, number | boolean | string | object>;
populate?: PopulateOptions | PopulateOptions[];
lean?: boolean;
}IPaginationResult<T>
interface IPaginationResult<T> {
data: T[];
meta: IPaginationMeta;
}IPaginationMeta
interface IPaginationMeta {
total: number;
lastPage: number;
currentPage: number;
perPage: number;
prev: number | null;
next: number | null;
}Advantages Over Other Solutions
Class-Based Design: Unlike function-based pagination utilities, our class-based approach allows for a more intuitive and chainable API.
TypeScript First: Built with TypeScript from the ground up, providing complete type safety without compromising on features.
Lightweight: No external dependencies beyond Mongoose itself, making it a lightweight addition to your project.
Flexible Query Options: Full support for all Mongoose query capabilities including complex filtering, sorting, and population.
Performance Optimized: Uses
Promise.allto run document fetching and count queries in parallel for better performance.Lean Option: Built-in support for lean queries, significantly improving performance when working with large datasets.
Comprehensive Metadata: Provides rich pagination metadata including total count, current page, last page, and navigation helpers.
Fluent API: Chainable methods make the API intuitive and easy to use, improving code readability.
Use Cases
- RESTful APIs: Implement standardized pagination for your API endpoints
- Admin Dashboards: Paginate large datasets in admin interfaces
- Data Tables: Power data tables with server-side pagination
- Search Results: Paginate search results efficiently
- Feed Systems: Implement "load more" or paginated content feeds
🔄 Comparison: @dakohhh/mongoose-paginator vs mongoose-paginate-v2
| Feature | @dakohhh/mongoose-paginator | mongoose-paginate-v2 |
|--------------------------|---------------------------------------------------------------------------------------------|-----------------------------------------------------|
| 🏗️ Architecture | Class-based with fluent, chainable methods | Plugin-based (requires schema.plugin) |
| ✅ TypeScript Support | ✅ First-class TypeScript support with full generic typing | ⚠️ Partial — requires community types |
| 🧩 Integration | No schema modification needed — use directly with any Mongoose model | Requires modification via schema.plugin |
| 🚀 Performance | Parallel execution of data & count queries using Promise.all() | Serial execution (slightly slower on large queries) |
| 🧠 Query Options | Advanced support: filtering, sorting, projection, population, lean queries | Basic support for filter, select, populate, lean |
| 📊 Pagination Metadata | Rich meta: total, perPage, currentPage, prev, next, lastPage | Provides basic pagination metadata |
| 🔄 Chainable API | ✅ Fluent API: .setPage().setLimit().setArgs() | ❌ Not supported |
| 🧼 Clean API | Supports destructuring (const { data, meta } = ...) | Partially supported |
| 🧪 Testability | Easily testable: no schema mutation, class-based structure | Harder to mock due to schema plugin requirement |
| ⚙️ Projection Support | ✅ Yes | ✅ Yes |
| 🔌 Populate Support | ✅ Yes | ✅ Yes |
| 💬 Lean Document Support | ✅ Yes | ✅ Yes |
| 📦 Dependencies | Zero dependencies beyond Mongoose | Depends on Mongoose and plugin structure |
| 🧠 Learning Curve | Slightly higher (class-based, customizable) | Very beginner-friendly |
| 📚 Community | New and growing | Large and established |
| 🔐 Schema Safety | Does not modify original schema | Alters schema with plugin |
License
ISC
Author
Wisdom Dakoh
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
