A query and aggregate pagination library for Mongoose with custom labels.
Easy-mongoose-paginate is a simple pagination library inspired by mongoose-paginate-v2 with two main improvements:
- Paginating both aggregate and find query with one package
- Adding a direct style usage in addition to plugin approach -> in progress
The below documentation is not perfect. Feel free to contribute. Having any fun usage of the package, feel free to update this readme
npm install easy-mongoose-paginate
Add paginate plugin to your schema and then use model paginateQuery
and paginateAggregate
methods:
const mongoose = require('mongoose');
const easyMongoosePaginate = require('easy-mongoose-paginate');
const userSchema = new mongoose.Schema({
/* your schema definition */
});
userSchema.plugin(easyMongoosePaginate);
const userModel = mongoose.model('user', userSchema);
//Find query
const users = await userModel.paginateQuery({}, { select: "email", limit: 10 })
//Aggregate query
const users = await userModel.paginateAggregate([], { page: 1, limit: 10 })
Easy-mongoose-paginate ship with it's own type definition. There is no need to install types for it.
import * as easyMongoosePaginate from 'easy-mongoose-paginate';
import { EasyPaginateModel } from 'mongoose';
const userSchema = new mongoose.Schema({
/* your schema definition */
});
userSchema.plugin(easyMongoosePaginate);
interface userDocument extends Document, IUserSchema { }
const userModel = mongoose.model<UserDocument, EasyPaginateModel<UserDocument>>('user', userSchema);
//Find query
const users = await userModel.paginateQuery({}, { select: "email", limit: 10 }) // Usage
//Aggregate query
const users: IPaginationResult<T> = await userModel.paginateAggregate([], { page: 2, limit: 10 }) // Usage
Wrap model with EasyPaginateModel. Make sure you have added easy mongoose paginate as a plugin to the model else you will get a type error for EasyPaginateModel.
//model.ts
@Schema({ timestamps: true })
export class Category extends BaseSchema {
@Prop({ required: true, unique: true })
name: string;
@Prop({ trim: true })
description: string;
}
const CategorySchema = SchemaFactory.createForClass(Category);
CategorySchema.plugin(easyMongoosePaginate);
export { CategorySchema };
//service.ts
import { EasyPaginateModel } from 'mongoose';
@Injectable()
export class CategoryService {
constructor(
@InjectModel(Category.name)
private categoryModel: EasyPaginateModel<Category>,
) {}
public async getAllCategories(query: CommonQuery) {
const categories = await this.categoryModel.paginateQuery(
{ isDeleted: false },
query,
);
console.log(categories)
//results
{
// result.docs
// result.totalDocs = 100
// result.limit = 10
// result.page = 1
// result.totalPages = 10
// result.hasNextPage = true
// result.nextPage = 2
// result.hasPrevPage = false
// result.prevPage = null
// result.pagingCounter = 1
};
}
}
Returns promise
Parameters
-
[query]
{Object} - Query criteria. Documentation -
[options]
{Object}-
[select]
{Object | String} - Fields to return (by default returns all fields). Documentation -
[collation]
{Object} - Specify the collation Documentation -
[sort]
{Object | String} - Sort order. Documentation -
[populate]
{Array | Object | String} - Paths which should be populated with other documents. Documentation -
[lean=false]
{Boolean} - Should return plain javascript objects instead of Mongoose documents? Documentation -
[page=1]
{Number} -
[limit=10]
{Number}, Any number less than 1 will return all documents -
[labels]
{Object} - Developers can provide custom labels for manipulating the response data. -
[allowDiskUse]
{Boolean} - Set this to true, which allows the MongoDB server to use more than 100 MB for query. This option can let you work around QueryExceededMemoryLimitNoDiskUseAllowed errors from the MongoDB server. (Default:False
)
-
Returns promise
Parameters
-
[stage]
{Array} - Aggregate pipeline stages. Documentation -
[options]
{Object}-
[project]
{Object | String} - Fields to return (by default returns all fields). Documentation -
[collation]
{Object} - Specify the collation Documentation -
[sort]
{Object | String} - Appends a new $sort operator to this aggregate pipeline. Documentation -
[page=1]
{Number} -
[lookup]
{Object} Add related fields in aggregate Documentation -
[limit=10]
{Number}, Any number less than 1 will return all documents -
[labels]
{Object} - Developers can provide custom labels for manipulating the response data. -
[allowDiskUse]
{Boolean} - Set this to true, which allows the MongoDB server to use more than 100 MB for query. This option can let you work around QueryExceededMemoryLimitNoDiskUseAllowed errors from the MongoDB server. (Default:False
)
-
Return value
Promise fulfilled with object having properties:
-
docs
{Array} - Array of documents -
totalDocs
{Number} - Total number of documents in collection that match a query -
limit
{Number} - Limit that was used -
hasPrevPage
{Bool} - Availability of prev page. -
hasNextPage
{Bool} - Availability of next page. -
page
{Number} - Current page number -
totalPages
{Number} - Total number of pages. -
prevPage
{Number} - Previous page number if available or NULL -
nextPage
{Number} - Next page number if available or NULL -
pagingCounter
{Number} - The starting index/serial/chronological number of first document in current page. (Eg: if page=2 and limit=10, then pagingCounter will be 11). Easy mongoose paginate uses a 1-based page index
Please note that the above properties can be renamed by setting labels attribute.
const options = {
page: 1,
limit: 10,
collation: {
locale: 'en',
},
};
const results = await Model.paginateQuery({}, options)
//results
{
// result.docs
// result.totalDocs = 100
// result.limit = 10
// result.page = 1
// result.totalPages = 10
// result.hasNextPage = true
// result.nextPage = 2
// result.hasPrevPage = false
// result.prevPage = null
// result.pagingCounter = 1
};
Users can customize the names of the object returned by modifying the labels.
- totalDocs
- docs
- limit
- page
- nextPage
- prevPage
- hasNextPage
- hasPrevPage
- totalPages
- pagingCounter
You should pass the names of the properties you wish to change using labels
object in options.
Same query with custom labels
const myCustomLabels = {
totalDocs: 'itemCount',
docs: 'itemsList',
limit: 'perPage',
page: 'currentPage',
nextPage: 'next',
prevPage: 'prev',
totalPages: 'pageCount',
pagingCounter: 'slNo',
};
const options = {
page: 1,
limit: 10,
labels: myCustomLabels,
};
const results = await Model.paginateAggregate([], options)
//results
{
// result.itemsList [here docs become itemsList]
// result.itemCount = 100 [here totalDocs becomes itemCount]
// result.perPage = 10 [here limit becomes perPage]
// result.currentPage = 1 [here page becomes currentPage]
// result.pageCount = 10 [here totalPages becomes pageCount]
// result.next = 2 [here nextPage becomes next]
// result.prev = null [here prevPage becomes prev]
// result.slNo = 1 [here pagingCounter becomes slNo]
// result.hasNextPage = true
// result.hasPrevPage = false
};
Using offset
and limit
:
const results = await Model.paginateQuery({}, { offset: 30, limit: 10 })
//results
{
// result.docs
// result.totalPages
// result.limit - 10
// result.offset - 30
};
var query = {};
var options = {
select: 'title date author',
sort: { date: -1 },
populate: 'author',
lean: true,
limit: 10,
};
const books = await Book.paginateQuery(query, options)
You can use limit=0
to get all the data:
const results = await Model.paginateAggregate([], { limit: 0 })
//results
{
// result.docs - all data
// result.totalDocs
// result.limit - 0
};
Set a label to "false" to remove it from the returned result
const results = await Model.paginateAggregate([], { limit: 0 }, {hasPrevPage: "false",hasNextPage: "false", pagingCounter: "false"})
//results - it does not include those fields.
{
// result.itemsList [here docs become itemsList]
// result.itemCount = 100 [here totalDocs becomes itemCount]
// result.perPage = 10 [here limit becomes perPage]
// result.currentPage = 1 [here page becomes currentPage]
// result.pageCount = 10 [here totalPages becomes pageCount]
// result.next = 2 [here nextPage becomes next]
// result.prev = null [here prevPage becomes prev]
};
To avoid repetition, you can override the default options or set some options globally. For specific query that should be different from the config you set globally. Pass the options to paginateQuery or paginate aggregate to override the global config.
import easyMongoosePaginate, { easyMongoosePaginateConfig } from 'easyMongoosePaginate';
//set options
easyMongoosePaginateConfig.globalOptions = { limit: 1 }
const results = await Model.paginateQuery([], { lean: true })
//results
{
// result.docs
// result.totalDocs
// result.limit = 1 instead of 10 which is the default
//...
};
Retrieve the current state of all the options the package is currently using. This is useful when you have set global options in a lot of places
import easyMongoosePaginate, { easyMongoosePaginateConfig } from 'easyMongoosePaginate';
const options = easyMongoosePaginateConfig.getOptions()
console.log(options)
//results
{
// sort: "",
// limit: 1,
// page: 1,
// select: "",
// populate: "",
// project: {},
// allowDiskUse: false,
// lean: false,
// ...
};
Sets the allowDiskUse option, which allows the MongoDB server to use more than 100 MB for query. This option can let you work around QueryExceededMemoryLimitNoDiskUseAllowed
errors from the MongoDB server.
Note that this option requires MongoDB server >= 4.4. Setting this option is a no-op for MongoDB 4.2 and earlier.
const options = {
limit: 10,
page: 1,
allowDiskUse: true,
};
const results = await Model.paginateQuery({}, options)
Below are some references to understand more about preferences,