I want to implement pagination for mongodb in node.js enviroment using offical mongodb package. I tried to find out on internet but all are mongoose based links. I dont want
Using the recommended pagination approach with limit() and skip() (see here):
const MongoClient = require('mongodb').MongoClient;
MongoClient.connect('http:localhost:27017').then((client) => {
const db = client.db(mongo.db);
db.collection('my-collection').find({}, {limit:10, skip:0}).then((documents) => {
//First 10 documents
console.log(documents);
});
db.collection('my-collection').find({}, {limit:10, skip:10}).then((documents) => {
//Documents 11 to 20
console.log(documents);
});
});
Here's a pagination function:
function studentsPerPage (pageNumber, nPerPage) {
return db.collection('students').find({},
{
limit: nPerPage,
skip: pageNumber > 0 ? ( ( pageNumber - 1 ) * nPerPage ) : 0
});
}
I am sending an API that is on MongoDb and Nodejs.
module.exports.fetchLoans = function(req, res, next) {
var perPage = 5;
var page = req.body.page || 1;
loans
.find({ userId: req.user._id})
.select("-emi")
.skip(perPage * page - perPage)
.limit(perPage)
.sort({ timestamp: -1 })
.exec(function(err, loan) {
if (loan != null) {
loans
.find({ userId: req.user._id})
.count()
.exec(function(err, count) {
if (count != null) {
res.json({
success: true,
loans: loan,
currentpage: page,
totalpages: Math.ceil(count / perPage)
});
} else {
console.log("Milestone Error: ", err);
res.json({ success: false, error: "Internal Server Error. Please try again." });
}
});
} else {
console.log("Milestone Error: ", err);
res.json({ success: false, error: "Internal Server Error. Please try again." });
}
});
};
In this code, you will have to provide page number on every hit.
Offset-based approach has a big flaw: if the results list has changed between calls to the API, the indices would shift and cause an item to be either returned twice or skipped and never returned
This problem is demonstrated at https://www.sitepoint.com/paginating-real-time-data-cursor-based-pagination/
Time-based pagination approach would be little better because results are no longer skipped. If you query the first page, and then a new item is deleted, it won’t shift the results in your second page and all is fine. However, this approach has a major flaw: what if there is more than one item that was created at the same time?
Best would be to use Cursor based pagination
Which can be implemented using any field in collection which is Unique, Orderable and Immutable.
_id
satisfy all Unique, Orderable and Immutable conditions. Based on this field we can sort and return page result with _id
of last document as the cusror for subsequent request.
curl https://api.mixmax.com/items?limit=2
const items = db.items.find({}).sort({
_id: -1
}).limit(2);
const next = items[items.length - 1]._id
res.json({ items, next })
when the user wants to get the second page, they pass the cursor (as next) on the URL:
curl https://api.mixmax.com/items?limit=2&next=590e9abd4abbf1165862d342
const items = db.items.find({
_id: { $lt: req.query.next }
}).sort({
_id: -1
}).limit(2);
const next = items[items.length - 1]._id
res.json({ items, next })
If we want to return results in a different order, such as the date the item then we will add sort=launchDate
to the querystring.
curl https://api.mixmax.com/items?limit=2&sort=launchDate
const items = db.items.find({}).sort({
launchDate: -1
}).limit(2);
const next = items[items.length - 1].launchDate;
res.json({ items, next })
For subsequent page request
curl https://api.mixmax.com/items?limit=2&sort=launchDate&next=2017-09-11T00%3A44%3A54.036Z
const items = db.items.find({
launchDate: { $lt: req.query.next }
}).sort({
_id: -1
}).limit(2);
const next = items[items.length - 1].launchDate;
res.json({ items, next });
If we launched a bunch of items on the same day and time? Now our launchDate
field is no longer unique and doesn’t satisfy Unique, Orderable and Immutable. condition. We can’t use it as a cursor field. But we could use two fields to generate the cursor.Since we know that the _id
field in MongoDB always satisfies the above three condition, we know that if we use it alongside our launchDate
field, the combination of the two fields would satisfy the requirements and could be together used as a cursor field.
curl https://api.mixmax.com/items?limit=2&sort=launchDate
const items = db.items.find({}).sort({
launchDate: -1,
_id: -1 // secondary sort in case there are duplicate launchDate values
}).limit(2);
const lastItem = items[items.length - 1];
// The cursor is a concatenation of the two cursor fields, since both are needed to satisfy the requirements of being a cursor field
const next = `${lastItem.launchDate}_${lastItem._id}`;
res.json({ items, next });
For subsequent page request
curl https://api.mixmax.com/items?limit=2&sort=launchDate&next=2017-09-11T00%3A44%3A54.036Z_590e9abd4abbf1165862d342
const [nextLaunchDate, nextId] = req.query.next.split(‘_’);
const items = db.items.find({
$or: [{
launchDate: { $lt: nextLaunchDate }
}, {
// If the launchDate is an exact match, we need a tiebreaker, so we use the _id field from the cursor.
launchDate: nextLaunchDate,
_id: { $lt: nextId }
}]
}).sort({
_id: -1
}).limit(2);
const lastItem = items[items.length - 1];
// The cursor is a concatenation of the two cursor fields, since both are needed to satisfy the requirements of being a cursor field
const next = `${lastItem.launchDate}_${lastItem._id}`;
res.json({ items, next });
Refefence: https://engineering.mixmax.com/blog/api-paging-built-the-right-way/
You can use skip
and limit
options to implement pagination
module.exports = (data)=>{
let page = parseInt(data.page);
let limit = parseInt(data.limit);
let skip = 0
if(page>1){
skip = (page * limit);
}
let mongoClient = require('mongodb').MongoClient;
mongoClient.connect('mongodb://localhost:27017').then((client) => {
let db = client.db('your-db');
db.collection('your-collection').find({}, {limit:limit, skip:skip}).then((documents) => {
console.log(documents);
});
});
};