nestjs-mongo-paginator
v0.1.0
Published
A MongoDB paginator utility for NestJS
Downloads
152
Maintainers
Readme
nestjs-mongo-paginator
A lightweight, type-safe pagination utility for NestJS + Mongoose that supports both offset-based and cursor-based pagination out of the box.
Features
- ✅ Offset pagination — classic
page/limitwith total count - ✅ Cursor pagination — efficient infinite-scroll / keyset pagination without
COUNT - ✅ TypeScript overloads — return type is inferred automatically based on the options you pass
- ✅ Zero config — drop in a single
paginate()call, no decorators or modules required - ✅ Mongoose v8 / v9 and NestJS v10 / v11 / v12 compatible
Installation
npm install nestjs-mongo-paginatorPeer dependencies — make sure these are already installed in your project:
npm install mongoose @nestjs/common
Quick Start
import { paginate } from 'nestjs-mongo-paginator';
// Offset pagination
const result = await paginate(this.userModel, { isActive: true }, {
page: 1,
limit: 20,
sort: { createdAt: -1 },
});
// Cursor pagination
const result = await paginate(this.userModel, { isActive: true }, {
mode: 'cursor',
limit: 20,
});Usage
Offset Pagination
Pass any standard Mongoose filter and options. The mode field defaults to 'offset' so you can omit it.
import { paginate, OffsetPaginateOptions, OffsetPaginatedResult } from 'nestjs-mongo-paginator';
@Injectable()
export class UserService {
constructor(@InjectModel(User.name) private userModel: Model<User>) {}
async findAll(page: number, limit: number): Promise<OffsetPaginatedResult<User>> {
return paginate(this.userModel, {}, {
page,
limit,
sort: { createdAt: -1 },
});
}
}Response shape:
{
"data": [...],
"total": 100,
"page": 1,
"limit": 20,
"totalPages": 5,
"hasNextPage": true,
"hasPrevPage": false
}Cursor Pagination
Set mode: 'cursor' to switch to cursor-based pagination. Pass the nextCursor from the previous response to get the next page. Ideal for infinite scroll and large datasets — no expensive COUNT query.
import { paginate, CursorPaginateOptions, CursorPaginatedResult } from 'nestjs-mongo-paginator';
@Injectable()
export class PostService {
constructor(@InjectModel(Post.name) private postModel: Model<Post>) {}
async findAll(cursor?: string): Promise<CursorPaginatedResult<Post>> {
return paginate(this.postModel, {}, {
mode: 'cursor',
limit: 20,
cursor, // undefined on first page
cursorField: '_id', // defaults to '_id'
direction: 'asc', // defaults to 'asc'
});
}
}Response shape:
{
"data": [...],
"nextCursor": "eyJpZCI6IjY2YTEifQ==",
"prevCursor": "eyJpZCI6IjY2YTAifQ==",
"hasNextPage": true,
"hasPrevPage": false
}Fetching pages sequentially:
// First page
const page1 = await paginate(model, {}, { mode: 'cursor', limit: 20 });
// Next page — pass nextCursor from previous result
const page2 = await paginate(model, {}, {
mode: 'cursor',
limit: 20,
cursor: page1.nextCursor,
});API Reference
paginate(model, filter, options)
| Parameter | Type | Description |
|---|---|---|
| model | Model<T> | Your Mongoose model |
| filter | FilterQuery<T> | Standard Mongoose query filter |
| options | OffsetPaginateOptions \| CursorPaginateOptions | Pagination options (see below) |
OffsetPaginateOptions
| Field | Type | Default | Description |
|---|---|---|---|
| mode | 'offset' | 'offset' | Discriminator — can be omitted |
| page | number | 1 | Page number (1-based) |
| limit | number | 10 | Documents per page |
| sort | Record<string, 1 \| -1> | {} | MongoDB sort object |
OffsetPaginatedResult<T>
| Field | Type | Description |
|---|---|---|
| data | HydratedDocument<T>[] | Documents for the current page |
| total | number | Total documents matching the filter |
| page | number | Current page number |
| limit | number | Page size |
| totalPages | number | Total number of pages |
| hasNextPage | boolean | Whether a next page exists |
| hasPrevPage | boolean | Whether a previous page exists |
CursorPaginateOptions
| Field | Type | Default | Description |
|---|---|---|---|
| mode | 'cursor' | — | Required discriminator |
| limit | number | 10 | Documents per page |
| cursor | string | undefined | Opaque cursor from previous nextCursor |
| cursorField | string | '_id' | Field used as the cursor key — must be unique and indexed |
| direction | 'asc' \| 'desc' | 'asc' | Sort direction for cursorField |
CursorPaginatedResult<T>
| Field | Type | Description |
|---|---|---|
| data | HydratedDocument<T>[] | Documents for the current page |
| nextCursor | string \| null | Pass to cursor to fetch the next page. null if no next page |
| prevCursor | string \| null | Cursor representing the start of the current page. null on the first page |
| hasNextPage | boolean | Whether a next page exists |
| hasPrevPage | boolean | Whether a previous page exists (i.e. a cursor was supplied) |
TypeScript
The paginate() function uses overloads so the return type is resolved at compile time — no casting needed.
// TypeScript knows this is OffsetPaginatedResult<User>
const offset = await paginate(userModel, {}, { page: 1, limit: 10 });
offset.totalPages; // ✅
// TypeScript knows this is CursorPaginatedResult<User>
const cursor = await paginate(userModel, {}, { mode: 'cursor', limit: 10 });
cursor.nextCursor; // ✅License
MIT
