How to implement pagination and total with mongoose - node.js

I have not found how to return total data included in the resultset. Is there any way to return total data? I need to confirm that the correct JSON data is returned.
What I want is to have something like this returned:
total: 40,
page: 1,
pageSize: 3,
books: [
{
_id: 1,
title: "达·芬奇密码 ",
author: "[美] 丹·布朗",
},
{
_id: 2,
title: "梦里花落知多少",
author: "郭敬明",
},
{
_id: 3,
title: "红楼梦",
author: "[清] 曹雪芹",
}
]
}
Current code:
router.get('/booksquery', (req, res) => {
var page = parseInt(req.query.page) || 1;
var limit = parseInt(req.query.limit) || 3;
Book.find({})
.sort({ update_at: -1 })
.skip((page-1) * limit)
.limit(limit)
.exec((err, doc) => {
if (err) {
res.json(err)
} else {
res.json({
total: doc.total,
page: page,
pageSize: limit,
books:doc,
});
}
})
})

Probably you could do something like this:
Only if current page index is 0 default and pagination starts with next page, that is 1, your could do .skip(page * limit) make sure .skip() and .limit() gets Number.
router.get("/booksquery", (req, res) => {
var page = parseInt(req.query.page) || 0; //for next page pass 1 here
var limit = parseInt(req.query.limit) || 3;
var query = {};
Book.find(query)
.sort({ update_at: -1 })
.skip(page * limit) //Notice here
.limit(limit)
.exec((err, doc) => {
if (err) {
return res.json(err);
}
Book.countDocuments(query).exec((count_error, count) => {
if (err) {
return res.json(count_error);
}
return res.json({
total: count,
page: page,
pageSize: doc.length,
books: doc
});
});
});
});
from above you will get response like below:
{
books: [
{
_id: 1,
title: "达·芬奇密码 ",
author: "[美] 丹·布朗"
},
{
_id: 2,
title: "梦里花落知多少",
author: "郭敬明"
},
{
_id: 3,
title: "红楼梦",
author: "[清] 曹雪芹"
}
],
total: 3,
page: 0,
pageSize: 3
}
if you use any condition and over that you want to make query and get result and total on that basics. do it inside var query ={} .
and the same query will be used for .count() also.
so you can get total count on the basics of that condition.

In case someone wants to see this using async/await in 2020, and some random query instead of an empty object. Remember that estimatedDocumentCount does not work using query filters, it needs to be countDocuments by using the query on it. Please check:
const { userId, page, limit } = req.headers;
const query = {
creator: userId,
status: {
$nin: ['deleted'],
},
};
const page_ = parseInt(page, 10) || 0;
const limit_ = parseInt(limit, 10) || 12;
const books = await BookModel.find(query)
.sort({ update_at: -1 })
.skip(page_ * limit_)
.limit(limit_);
const count = await BookModel.countDocuments(query);

count() is deprecated(form mongoose#5.x). estimatedDocumentCount() is faster than using countDocuments() for large collections because estimatedDocumentCount() uses collection metadata rather than scanning the entire collection.
Reference: https://mongoosejs.com/docs/api/model.html#model_Model.estimatedDocumentCount
So the code is perfect just replace countDocuments to estimatedDocumentCount
router.get("/booksquery", (req, res) => {
var page = parseInt(req.query.page) || 0; //for next page pass 1 here
var limit = parseInt(req.query.limit) || 3;
var query = {};
Book.find(query)
.sort({ update_at: -1 })
.skip(page * limit) //Notice here
.limit(limit)
.exec((err, doc) => {
if (err) {
return res.json(err);
}
Book.estimatedDocumentCount(query).exec((count_error, count) => {
if (err) {
return res.json(count_error);
}
return res.json({
total: count,
page: page,
pageSize: doc.length,
books: doc
});
});
});
});

Related

server side pagination in node js and mongo db

please help me with this server side pagination in node js and mongo db
function getServiceQualityAnex(req, res, next) {
if (req.query.code != null) {
ServiceQualityAnex.find({ location: req.query.code }).sort({ _id: -1 }).select('-hash')
.then(serviceQualityAnexC => res.json(serviceQualityAnexC))
.catch(err => {
res.sendStatus(404);
next(err)
});
} else {
ServiceQualityAnex.find({}).sort({ _id: -1 }).select('-hash')
.then(serviceQualityAnexC => res.json(serviceQualityAnexC))
.catch(err => {
res.sendStatus(404);
next(err)
});
}
}
let page = Number(req.query.page);
page = page ? page : 0;
let limit = parseInt(req.query.limit);
const result = {};
let startIndex = page * limit;
if (startIndex > 0) {
result.previous = {
page: page - 1,
limit: limit,
};
}
let receive = await Model.find()
.sort("-_id")
.skip(startIndex)
.limit(limit)
.exec();
i want to do a server side pagination on server side my front end api is http://localhost:3000/getServiceQualityAnexJoin/ the function which is mentioned above is combing 2 tables are returning . my data is very huge and i want to add a server side pagination
You did not specify all the requirements in your question but what i precieve is that you want to do pagination on server side with nodejs in mongodb
Here is what you need to do:
const getServiceQualityAnex = async (request, response) => {
try {
const id = request.params.id;
let { page } = request.query; //this indicates the page you are requesting for in pagination
if (!page)
page = 1; //by default it is one
const result = await ServiceQualityAnex.aggregate([
{
$match: {
"_id": mongoose.Types.ObjectId(id)
}
},
{
$project: {
"_id": 1,
.
.
.
// all the fields you want to get
}
},
{
$facet: { //facet is the aggregation pipeline in mongodb through which you can achieve pagination
metadata: [{ $count: 'total' }],
data: [
{
$skip: (Number(page) - 1) * Number(20)
},
{
$limit: 20 //20 records limit per page
},
]
}
}
]);
console.log("result :", result[0].data);
return response
.status(200)
.json(
{
result: result[0].data,
meta: {
current_page: page,
total_pages: Math.ceil(
(Number(result[0].metadata.length === 0 ? 0 : result[0].metadata[0].total))
/ (Number(20))),
total_count: result[0].metadata.length === 0 ? 0 : result[0].metadata[0].total,
}
}
);
} catch (error) {
console.log(error);
response.status(500).json({
error: "Something went wrong",
});
}
}
If you don't know anything about aggregation then you must visit this site:
MongoDB aggregation framework

I want fetch the documents in reverse order from collection in mongoose

I want to fetch the latest doc from the collection first in mongoose. I am using mongoose with nodejs/expressjs
Also, I am doing pagination on the server side so I can't reverse this after fetching.
exports.getProjects = (req, res, next) => {
const page = +req.query.page || 1;
let totalItems;
Project
.find()
.countDocuments()
.then(numProducts => {
totalItems = numProducts;
return Project.find()
.skip((page - 1) * ITEMS_PER_PAGE)
.limit(ITEMS_PER_PAGE)
})
.then(result => {
return res.status(200).json({
projects: result,
currentPage: page,
itemCount: totalItems,
itemsPerPage: ITEMS_PER_PAGE,
});
})
.catch(err => {
console.log(err);
})
};
You can include sort in your query . you did not mention any attribute eligible for sorting like date, or any number. you can use aggregation as below
const projectscursor = await Project.aggregate([
{$match:Query},
{$sort:{field:1}},
{$skip:number},
{$limit:somenumber}
])
I guess you're looking for sort() function in mongoose. You can simply sort your collection by using any of the following techniques.
Project.find({}).sort('test').exec(function(err, docs) { ... });
Project.find({}).sort([['date', -1]]).exec(function(err, docs) { ... });
Project.find({}).sort({test: 1}).exec(function(err, docs) { ... });
Project.find({}, null, {sort: {date: 1}}, function(err, docs) { ... });
Try one of the upper techniques according to your requirements with the following code for proper pagination.
app.get('/projects', async (req, res) => {
// destructure page and limit and set default values
const { page = 1, limit = 10 } = req.query;
try {
// execute query with page and limit values
const projects = await Project.find()
.sort({ field : criteria})
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
// get total documents in the Project collection
const count = await Project.countDocuments();
// return response with posts, total pages, and current page
res.json({
projects,
totalPages: Math.ceil(count / limit),
currentPage: page
});
} catch (err) {
console.error(err.message);
}
});
Where
criteria can be "asc", "desc", "ascending", "descending", 1, or -1

Wait for mongoose.find inside foreach loop inside callback

How do I wait till data is populated in finalresult before going further?
I tried async and await but did not work as expected and on top of that I am new to nodejs express
exports.getporderlist = (req, res) => {
page = req.params.page || "1";
skip = (page - 1) * 10;
if (req.profile.role.includes(1)) {
Order.find({ status: { $in: [0, 2] } })
.select("_id updatedAt items salesman")
.populate("customer", "customer_name phone")
.sort({ updatedAt: -1 })
.skip(skip)
.limit(10)
.exec((err, orders) => {
if (err) {
return res.status(400).json({
error: "No Orders found",
});
}
let finalresult = [];
orders.forEach((oelement) => {
total = 0;
salesman = oelement.salesman.split(" ")[0];
itemscount = oelement.items.length;
placeorder = true;
oelement.items.forEach((element) => {
total += element.price * element.quantity;
});
//Wait for the bellow data variable to finish populating finalresult
const data = Design.find()
.select("_id sm lm")
.where("_id")
.in(oelement.items)
.exec((err, orders) => {
finalresult.push(orders);
console.log(orders);
});
});
console.log(1);
res.json(finalresult);//getting empty finalresult array on call
});
} else {
res.json({ Message: "Go away" });
}
};
The exec() function do returns promise, so you can user async/await to call it.
Your code will look like this:
exports.getporderlist = async (req, res) => {
try {
if (req.profile.role.includes(1)) {
page = Number(req.params.page) || 1;
skip = (page - 1) * 10;
const orders = await Order.find({
status: {
$in: [0, 2]
}
})
.select("_id updatedAt items salesman")
.populate("customer", "customer_name phone")
.sort({
updatedAt: -1
})
.skip(skip)
.limit(10)
.exec();
let finalresult = [];
for (const oelement of orders) {
let total = 0;
salesman = oelement.salesman.split(" ")[0];
itemscount = oelement.items.length;
placeorder = true;
oelement.items.forEach((element) => {
total += element.price * element.quantity;
});
const data = await Design.find()
.select("_id sm lm")
.where("_id")
.in(oelement.items)
.exec();
finalresult.push(data);
}
res.json(finalresult);
} else {
res.json({
Message: "Go away"
});
}
} catch (err) {
return res.status(400).json({
error: "No Orders found",
});
}
}
you can always call async functions with async/await inside for ..of loops. You can read more here
P.s. Couldn't get a chance to run the code.. Let me know if you have any doubts :)

Paginated results in mongoose with filters on reference document

I have a user and post document as follow:
user: {
"name": "Test",
interests: [
"Sports",
"Movies",
"Running"
]
}
post: {
"title": "testing",
"author": ObjectId(32432141234321411) // random hash
}
I want to query posts and fetch all those posts with author have "sports", "Running" as interests and this will be a paginated query.
How can I do so in mongoose and if not what alternative shall I use ?
Pagination using limit and skip
var limit = 5;
var page = 0; // 1,2,3,4
return Model.find({
/* Some query */
})
.limit(limit)
.skip(limit * page)
.exec().then((data) => {
console.log(data);
});
Try this
const findUser = (interests) => {
return User.find({
interests: {
$in: interests
}
}).exec();
};
const findPost = (query, page = 0) => {
const limit = 5;
return Model.find(query)
.limit(limit)
.skip(limit * page)
.exec();
};
var execute = async () => {
const users = await findUser(["Sports", "Movies", ]);
users.forEach(user => {
user.post = await findPost({
"post.author": user._id
});
});
return users;
}
I used following approach though giving answer with async/await approach but I actually used it using promises.
const fetchPosts = async (req, res) => {
//First find users having those interests.
const users = await User.find({
interests: {
"$in": ["Sports", "Movies"]
}
})
.select('_id')
.exec();
// map over array of objects to get array of ids
const userIds = users.map(u => u._id);
// Then run an in filter on Post collection against author and
//userIds
const posts = await Post.find({
author: {
"$in": [userIds]
}
})
.limit(15)
.skip(0)
.exec();
}

what is the correct way to use async await with mariadb query in NodeJs?

I'm new to async/await.
I'm trying to use async and await but the query is not waiting and it happens at last and the page renders before the query so I can't get the correct answer on rendered page.
Here is my code before using async await
orderMiddleware.newOrder = function (req, res) {
var total = 0
var curr_total = 0
// get items from cart
c.query('select * from cart where user_id=:userId',
{ userId: req.user.ID }, function (err, cart) {
if (err) {
console.log(err)
} else {
cart.forEach(function (item) {
// Find item from DB and check their price
c.query('select * from products where id=:id',
{ id: item.item_id },
function (err, foundItem) {
if (err) {
console.log(err)
} else {
curr_total = foundItem[0].price * item.quantity
console.log("currenttotal" + curr_total)
total += curr_total
console.log(total)
}
})
})
console.log(total)
console.log(curr_total)
// Calculate total price
// Multiply all items with their quantity
res.render('orders/new', { cart: cart, total: total })
}
})
}
However this doesn't work properly. console.log(total) happens before the query so the result is zero and it renders zero in the rendered page.
Same thing happens if I use async. Am I using it wrong?
After using async await-
orderMiddleware.newOrder = async (req, res) => {
var total = 0
var curr_total = 0
// get items from cart
var A= c.query('select * from cart where user_id=:userId',
{ userId: req.user.ID }, async (err, cart) => {
if (err) {
console.log(err)
} else {
cart.forEach(async (item) => {
// Find item from DB and check their price
await c.query('select * from products where id=:id',
{ id: item.item_id },
async (err, foundItem) =>{
if (err) {
console.log(err)
} else {
curr_total = foundItem[0].price * item.quantity
console.log("currenttotal" + curr_total)
total += curr_total
console.log(total)
}
})
})
await console.log(total)
// await console.log(curr_total)
// Calculate total price
// Multiply all items with their quantity
await res.render('orders/new', { cart: cart, total: total })
}
})
}
I tried without using callbacks like:
var A= c.query('select * from cart where user_id=:userId',
{ userId: req.user.ID })
but then how can I get the output of the query?
console.log(A) shows different results.
You can't because the functions don't return promises. You can promisify those function using a thirty-part library (for example es6-promisify) or you can wrap those by yourself.
Once a function returns a Promise, you can await it.
For example, for the above, a solution could be the following:
const execQuery = (sql, params) => new Promise((resolve, reject) => {
query(sql, params, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
const logCartItem = async (userId) => {
try {
const items = await execQuery('select * from cart where user_id=:userId', { userId });
items.forEach(console.log);
} catch (error) {
console.error(error);
}
};
Assuming you're using the node-mariasql package. Short answer is you can't use async/await because the package does not support Promises.
With node-mariasql it's easy to use promisify
const util = require('util')
const asyncQuery = util.promisify(c.query);
const rows = await asyncQuery.call(c, 'SELECT product FROM products WHERE id = :id', { id }, { useArray: false, metaData: false })

Resources