web-dev-qa-db-fra.com

Comment paginer avec Mongoose dans Node.js?

J'écris une webapp avec Node.js et mangouste. Comment puis-je paginer les résultats d'un appel .find()? Je voudrais une fonctionnalité comparable à "LIMIT 50,100" en SQL.

182
Thomas

Après avoir examiné de plus près l'API Mongoose avec les informations fournies par Rodolphe, j'ai trouvé cette solution

MyModel.find(query, fields, { skip: 10, limit: 5 }, function(err, results) { ... });
194
Thomas

Je suis très déçu par les réponses acceptées à cette question. Cela ne sera pas à l'échelle. Si vous lisez les petits caractères sur cursor.skip ():

La méthode cursor.skip () est souvent coûteuse, car le serveur doit parcourir le début de la collection ou de l'index pour obtenir la position de décalage ou de saut avant de commencer à renvoyer le résultat. Au fur et à mesure que le décalage (par exemple, numéro de page ci-dessus) augmente, cursor.skip () devient plus lent et utilise davantage le processeur. Cursor.skip () peut devenir IO lié.

Pour réaliser la pagination de manière évolutive, associez une limite () à au moins un critère de filtre, une date createdOn convient à de nombreuses fins. 

MyModel.find( { createdOn: { $lte: request.createdOnBefore } } )
.limit( 10 )
.sort( '-createdOn' )
248
Chris Hinkle

Pagination utilisant mangouste, express et jade - http://madhums.me/2012/08/20/pagination-using-mongoose-express-and-jade/

var perPage = 10
  , page = Math.max(0, req.param('page'))

Event.find()
    .select('name')
    .limit(perPage)
    .skip(perPage * page)
    .sort({
        name: 'asc'
    })
    .exec(function(err, events) {
        Event.count().exec(function(err, count) {
            res.render('events', {
                events: events,
                page: page,
                pages: count / perPage
            })
        })
    })
74
Madhusudhan

Vous pouvez enchaîner comme ça:

var query = Model.find().sort('mykey', 1).skip(2).limit(5)

Exécutez la requête en utilisant exec

query.exec(callback);
50
Rodolphe

Vous pouvez utiliser un petit paquet nommé Mongoose Paginate qui facilite les choses.

$ npm install mongoose-paginate

Après dans vos routes ou contrôleur, ajoutez simplement:

/**
 * querying for `all` {} items in `MyModel`
 * paginating by second page, 10 items per page (10 results, page 2)
 **/

MyModel.paginate({}, 2, 10, function(error, pageCount, paginatedResults) {
  if (error) {
    console.error(error);
  } else {
    console.log('Pages:', pageCount);
    console.log(paginatedResults);
  }
}
31
Clément Renaud

Mieux vaut tard que jamais.

var pageOptions = {
    page: req.query.page || 0,
    limit: req.query.limit || 10
}

sexyModel.find()
    .skip(pageOptions.page*pageOptions.limit)
    .limit(pageOptions.limit)
    .exec(function (err, doc) {
        if(err) { res.status(500).json(err); return; };
        res.status(200).json(doc);
    })

Dans ce cas, vous pouvez ajouter la requête page et/ou limit à votre URL http. Exemple ?page=0&limit=25

BTW La pagination commence par 0

27
CENT1PEDE

Ceci est un exemple d'exemple que vous pouvez essayer,

var _pageNumber = 2,
  _pageSize = 50;

Student.count({},function(err,count){
  Student.find({}, null, {
    sort: {
      Name: 1
    }
  }).skip(_pageNumber > 0 ? ((_pageNumber - 1) * _pageSize) : 0).limit(_pageSize).exec(function(err, docs) {
    if (err)
      res.json(err);
    else
      res.json({
        "TotalCount": count,
        "_Array": docs
      });
  });
 });
14
user123456789

Essayez d’utiliser la fonction mangouste pour la pagination. Limite est le nombre d'enregistrements par page et le nombre de pages.

var limit = parseInt(body.limit);
var skip = (parseInt(body.page)-1) * parseInt(limit);

 db.Rankings.find({})
            .sort('-id')
            .limit(limit)
            .skip(skip)
            .exec(function(err,wins){
 });
9
Karthick

C'est ce que je l'ai fait sur le code

var paginate = 20;
var page = pageNumber;
MySchema.find({}).sort('mykey', 1).skip((pageNumber-1)*paginate).limit(paginate)
    .exec(function(err, result) {
        // Write some stuff here
    });

C'est comme ça que je l'ai fait.

8
Indra Santosa

Voici une version que je joins à tous mes modèles. Cela dépend de soulignement pour plus de commodité et async pour les performances. L'option permet de sélectionner et de trier les champs en utilisant la syntaxe de la mangouste.

var _ = require('underscore');
var async = require('async');

function findPaginated(filter, opts, cb) {
  var defaults = {skip : 0, limit : 10};
  opts = _.extend({}, defaults, opts);

  filter = _.extend({}, filter);

  var cntQry = this.find(filter);
  var qry = this.find(filter);

  if (opts.sort) {
    qry = qry.sort(opts.sort);
  }
  if (opts.fields) {
    qry = qry.select(opts.fields);
  }

  qry = qry.limit(opts.limit).skip(opts.skip);

  async.parallel(
    [
      function (cb) {
        cntQry.count(cb);
      },
      function (cb) {
        qry.exec(cb);
      }
    ],
    function (err, results) {
      if (err) return cb(err);
      var count = 0, ret = [];

      _.each(results, function (r) {
        if (typeof(r) == 'number') {
          count = r;
        } else if (typeof(r) != 'number') {
          ret = r;
        }
      });

      cb(null, {totalCount : count, results : ret});
    }
  );

  return qry;
}

Attachez-le à votre schéma de modèle.

MySchema.statics.findPaginated = findPaginated;
5
kberg

Le moyen le plus simple et le plus rapide consiste à paginer avec l’objet objectId.

Condition de charge initiale

condition = {limit:12, type:""};

Prendre le premier et le dernier ObjectId à partir des données de réponse

Page suivante condition

condition = {limit:12, type:"next", firstId:"57762a4c875adce3c38c662d", lastId:"57762a4c875adce3c38c6615"};

Page suivante condition

condition = {limit:12, type:"next", firstId:"57762a4c875adce3c38c6645", lastId:"57762a4c875adce3c38c6675"};

En mangouste

var condition = {};
    var sort = { _id: 1 };
    if (req.body.type == "next") {
        condition._id = { $gt: req.body.lastId };
    } else if (req.body.type == "prev") {
        sort = { _id: -1 };
        condition._id = { $lt: req.body.firstId };
    }

var query = Model.find(condition, {}, { sort: sort }).limit(req.body.limit);

query.exec(function(err, properties) {
        return res.json({ "result": result);
});
2
byteC0de

La meilleure approche (OMI) consiste à utiliser ignorer et limiter MAIS dans un nombre limité de collections ou de documents. 

Pour effectuer la requête dans des documents limités, nous pouvons utiliser un index spécifique, tel que l'index sur un champ de type DATE. Voir ci-dessous

let page = ctx.request.body.page || 1
let size = ctx.request.body.size || 10
let DATE_FROM = ctx.request.body.date_from
let DATE_TO = ctx.request.body.date_to

var start = (parseInt(page) - 1) * parseInt(size)

let result = await Model.find({ created_at: { $lte: DATE_FROM, $gte: DATE_TO } })
    .sort({ _id: -1 })
    .select('<fields>')
    .skip( start )
    .limit( size )        
    .exec(callback)
2
Nazmul Hassan

Ceci est un exemple de fonction permettant d’obtenir le résultat du modèle de compétences avec options de pagination et de limitation

 export function get_skills(req, res){
     console.log('get_skills');
     var page = req.body.page; // 1 or 2
     var size = req.body.size; // 5 or 10 per page
     var query = {};
     if(page < 0 || page === 0)
     {
        result = {'status': 401,'message':'invalid page number,should start with 1'};
        return res.json(result);
     }
     query.skip = size * (page - 1)
     query.limit = size
     Skills.count({},function(err1,tot_count){ //to get the total count of skills
      if(err1)
      {
         res.json({
            status: 401,
            message:'something went wrong!',
            err: err,
         })
      }
      else 
      {
         Skills.find({},{},query).sort({'name':1}).exec(function(err,skill_doc){
             if(!err)
             {
                 res.json({
                     status: 200,
                     message:'Skills list',
                     data: data,
                     tot_count: tot_count,
                 })
             }
             else
             {
                 res.json({
                      status: 401,
                      message: 'something went wrong',
                      err: err
                 })
             }
        }) //Skills.find end
    }
 });//Skills.count end

}

2
Venkatesh Somu

Le plugin le plus simple pour la pagination.

https://www.npmjs.com/package/mongoose-paginate-v2

Ajoutez un plugin à un schéma, puis utilisez la méthode model paginate:

var mongoose         = require('mongoose');
var mongoosePaginate = require('mongoose-paginate-v2');

var mySchema = new mongoose.Schema({ 
    /* your schema definition */ 
});

mySchema.plugin(mongoosePaginate);

var myModel = mongoose.model('SampleModel',  mySchema); 

myModel.paginate().then({}) // Usage
2
Arv Ot

Solution de pagination simple et puissante

async getNextDocs(last_doc_id?: string) {
    let docs

    if (!last_doc_id) {
        // get first 5 docs
        docs = await MySchema.find().sort({ _id: -1 }).limit(5)
    }
    else {
        // get next 5 docs according to that last document id
        docs = await MySchema.find({ _id: { $lt: last_doc_id} }).sort({ _id: -1 }).limit(5)
    }
    return docs
}
  1. Si vous ne fournissez pas le dernier ID de document à la méthode, vous obtiendrez les 5 derniers documents.
  2. Si vous avez fourni le dernier identifiant de document, vous obtiendrez les 5 prochains documents.
1
WasiF

Ci-dessus, la réponse est valable. 

Juste un complément pour quiconque est dans l'attente asynchrone plutôt que. promettre !!

const findAllFoo = async (req, resp, next) => {
    const pageSize = 10;
    const currentPage = 1;

    try {
        const foos = await FooModel.find() // find all documents
            .skip(pageSize * (currentPage - 1)) // we will not retrieve all records, but will skip first 'n' records
            .limit(pageSize); // will limit/restrict the number of records to display

        const numberOfFoos = await FooModel.countDocuments(); // count the number of records for that model

        resp.setHeader('max-records', numberOfFoos);
        resp.status(200).json(foos);

    } catch (err) {
        resp.status(500).json({
            message: err
        });
    }
};
1

Vous pouvez écrire une requête comme celle-ci. 

mySchema.find().skip((page-1)*per_page).limit(per_page).exec(function(err, articles) {
        if (err) {
            return res.status(400).send({
                message: err
            });
        } else {
            res.json(articles);
        }
    });

page: numéro de page provenant du client en tant que paramètres de demande.
per_page: nombre de résultats affichés par page 

Si vous utilisez la pile MEAN, les articles suivants du blog fournissent la plupart des informations permettant de créer une pagination en tête de réseau à l’aide de bootstrap angular-UI et en utilisant des méthodes de saut et de limitation mongoose dans le backend.

voir: https://techpituwa.wordpress.com/2015/06/06/mean-js-pagination-with-angular-ui-bootstrap/

0
Manoj Sanjeewa
app.get("/:page",(req,res)=>{
        post.find({}).then((data)=>{
            let per_page = 5;
            let num_page = Number(req.params.page);
            let max_pages = Math.ceil(data.length/per_page);
            if(num_page == 0 || num_page > max_pages){
                res.render('404');
            }else{
                let starting = per_page*(num_page-1)
                let ending = per_page+starting
                res.render('posts', {posts:data.slice(starting,ending), pages: max_pages, current_page: num_page});
            }
        });
});
0
jvitoroc

vous pouvez aussi utiliser la ligne de code suivante

per_page = parseInt(req.query.per_page) || 10
page_no = parseInt(req.query.page_no) || 1
var pagination = {
  limit: per_page ,
  skip:per_page * (page_no - 1)
}
users = await User.find({<CONDITION>}).limit(pagination.limit).skip(pagination.skip).exec()

ce code fonctionnera dans la dernière version de mongo

0
Apoorv

Si vous utilisez la mangouste comme source pour une api reposante, jetez un œil à ' restify-mongoose ' et à ses requêtes. Il a exactement cette fonctionnalité intégrée. 

Toute requête sur une collection fournit des en-têtes utiles ici. 

test-01:~$ curl -s -D - localhost:3330/data?sort=-created -o /dev/null
HTTP/1.1 200 OK
link: </data?sort=-created&p=0>; rel="first", </data?sort=-created&p=1>; rel="next", </data?sort=-created&p=134715>; rel="last"
.....
Response-Time: 37

Donc, en gros, vous obtenez un serveur générique avec un temps de chargement relativement linéaire pour les requêtes sur les collections. C'est génial et c'est quelque chose à regarder si vous voulez aller dans une implémentation propre. 

0

A pu obtenir des résultats avec async/wait aussi.

Exemple de code ci-dessous utilisant un gestionnaire asynchrone avec hapi v17 et mongoose v5

{
            method: 'GET',
            path: '/api/v1/paintings',
            config: {
                description: 'Get all the paintings',
                tags: ['api', 'v1', 'all paintings']
            },
            handler: async (request, reply) => {
                /*
                 * Grab the querystring parameters
                 * page and limit to handle our pagination
                */
                var pageOptions = {
                    page: parseInt(request.query.page) - 1 || 0, 
                    limit: parseInt(request.query.limit) || 10
                }
                /*
                 * Apply our sort and limit
                */
               try {
                    return await Painting.find()
                        .sort({dateCreated: 1, dateModified: -1})
                        .skip(pageOptions.page * pageOptions.limit)
                        .limit(pageOptions.limit)
                        .exec();
               } catch(err) {
                   return err;
               }

            }
        }
0
csichar
**//localhost:3000/asanas/?pageNo=1&size=3**

//requiring asanas model
const asanas = require("../models/asanas");


const fetchAllAsanasDao = () => {
    return new Promise((resolve, reject) => {

    var pageNo = parseInt(req.query.pageNo);
    var size = parseInt(req.query.size);
    var query = {};
        if (pageNo < 0 || pageNo === 0) {
            response = {
                "error": true,
                "message": "invalid page number, should start with 1"
            };
            return res.json(response);
        }
        query.skip = size * (pageNo - 1);
        query.limit = size;

  asanas
            .find(pageNo , size , query)
        .then((asanasResult) => {
                resolve(asanasResult);
            })
            .catch((error) => {
                reject(error);
            });

    });
}
0
chetan

Vous pouvez soit utiliser skip () et limit (), mais c'est très inefficace. Une meilleure solution consisterait à trier les champs indexés plus limit (). Chez Wunderflats, nous avons publié une petite bibliothèque ici: https://github.com/wunderflats/goosepage Elle utilise la première méthode.

0
Jan Hase