¿Cómo paginar con Mongoose en Node.js?


232

Estoy escribiendo una aplicación web con Node.js y mangosta. ¿Cómo puedo paginar los resultados que obtengo de una .find()llamada? Me gustaría una funcionalidad comparable a la "LIMIT 50,100"de SQL.


Use la propiedad de omitir y limitar mientras busca datos de la colección.
Arun Sahani

Este enlace puede ser usado para usted en detalles. laxmanchavda.blogspot.com/2018/06/...
laxman

Respuestas:


278

Estoy muy decepcionado por las respuestas aceptadas en esta pregunta. Esto no escalará. Si lees la letra pequeña en cursor.skip ():

El método cursor.skip () a menudo es costoso porque requiere que el servidor camine desde el comienzo de la colección o índice para obtener la posición de desplazamiento u omisión antes de comenzar a devolver el resultado. A medida que aumenta el desplazamiento (por ejemplo, el número de página anterior), cursor.skip () se volverá más lento e intensivo en CPU. Con colecciones más grandes, cursor.skip () puede convertirse en un enlace IO.

Para lograr la paginación de manera escalable, combine un límite () junto con al menos un criterio de filtro, una fecha de creación se adapta a muchos propósitos.

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

105
Pero, ¿cómo obtendría la página dos de esa consulta sin omitir? Si está viendo 10 resultados por página y hay 100 resultados, ¿cómo define el valor de desplazamiento u omisión? No está respondiendo a la pregunta de paginación, por lo que no puede sentirse "decepcionado", aunque es una precaución válida. Aunque el mismo problema está en el desplazamiento de MySQL, límite. Tiene que atravesar el árbol hasta el desplazamiento antes de devolver los resultados. Tomaría esto con un grano de sal, si sus conjuntos de resultados son inferiores a 1mil y no hay un impacto de rendimiento conservable, use skip ().
Lex

13
Soy un novato cuando se trata de mangosta / mongodb, pero para responder a la pregunta de Lex, parece que, como los resultados están ordenados por ' -createdOn', reemplazaría el valor de request.createdOnBeforecon el menor valor createdOndevuelto en el conjunto de resultados anterior, y luego consulta.
Terry Lewis

99
@JoeFrambach Solicitar basado en createdOn parece problemático. Skip fue incrustado por una razón. Los documentos solo advierten del impacto en el rendimiento de recorrer el índice btree, que es el caso con todos los DBMS. Para los usuarios pregunta "algo comparable MySQL a LIMIT 50,100" .skip es exactamente correcto.
Lex

8
Si bien es interesante, un problema con esta respuesta, como señala el comentario de @Lex, es que solo puede saltar "hacia adelante" o "hacia atrás" a través de los resultados; no puede tener "páginas" a las que pueda saltar (por ejemplo, página 1, página 2 , Página 3) sin hacer múltiples consultas secuenciales para averiguar desde dónde comenzar la paginación, lo que sospecho que será más lento en la mayoría de los casos que simplemente usar skip. Por supuesto, es posible que no necesite agregar la capacidad de saltar a páginas específicas.
Iain Collins

20
Esta respuesta contiene puntos interesantes, pero no responde la pregunta original formulada.
alimentado a vapor el

227

Después de echar un vistazo más de cerca a la API de Mongoose con la información proporcionada por Rodolphe, descubrí esta solución:

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

21
¿Qué hay de "contar"? Necesitas eso para saber cuántas páginas hay.
Aleksey Saatchi

36
No escala
Chris Hinkle

44
La explicación de Chris Hinkle por qué esto no escala: stackoverflow.com/a/23640287/165330 .
Imme

77
@ChrisHinkle Este parece ser el caso con todos los DBMS. El comentario de Lex debajo de la respuesta vinculada parece explicar más.
csotiriou

2
@Avij sí. He usado un identificador para eso. lo que haces allí es enviar la última identificación de registros al servidor y obtener algunos registros con una identificación mayor que la enviada. Como Id está indexado, será mucho más rápido
George Bailey

108

Paginación con mangosta, express y jade: aquí hay un enlace a mi blog con más detalle

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
            })
        })
    })

26
Gracias por publicar tu respuesta! Asegúrese de leer atentamente las preguntas frecuentes sobre autopromoción. También tenga en cuenta que es necesario que publique un descargo de responsabilidad cada vez que enlace a su propio sitio / producto.
Andrew Barber

Math.max(0, undefined)volverá undefined, Esto funcionó para mí:let limit = Math.abs(req.query.limit) || 10; let page = (Math.abs(req.query.page) || 1) - 1; Schema.find().limit(limit).skip(limit * page)
Monfa.red

55

Puedes encadenar así:

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

Ejecute la consulta usando exec

query.exec(callback);

Gracias por su respuesta, ¿cómo se agrega la devolución de llamada con el resultado?
Thomas

2
execFind (function (... por ejemplo: var page = req.param('p'); var per_page = 10; if (page == null) { page = 0; } Location.count({}, function(err, count) { Location.find({}).skip(page*per_page).limit(per_page).execFind(function(err, locations) { res.render('index', { locations: locations }); }); });
todd

99
nota: esto no funcionará en mangosta , pero funcionará en mongodb-native-driver.
Jesse

39

En este caso, puede agregar la consulta pagey / o limitsu URL como una cadena de consulta.

Por ejemplo:
?page=0&limit=25 // this would be added onto your URL: http:localhost:5000?page=0&limit=25

Como sería un String, necesitamos convertirlo en a Numberpara nuestros cálculos. Hagámoslo usando el parseIntmétodo y también proporcionemos algunos valores predeterminados.

const pageOptions = {
    page: parseInt(req.query.page, 10) || 0,
    limit: parseInt(req.query.limit, 10) || 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);
    });

Por cierto, la paginación comienza con0


55
agregue el `{page: parseInt (req.query.page) || 0, ...} al parámetro.
imalik8088

@ imalik8088 Gracias, sin embargo, los parámetros de cadena AFAIK se manejan automáticamente mongoose.
CENT1PEDE

1
Esperaba el comportamiento, pero en mi caso no podía encubrirme y me mostraba un error
imalik8088

@ imalik8088 Eso es raro. Tal vez si pudiera mostrar un error de reproducción, puedo editar mi respuesta. Gracias.
CENT1PEDE

2
¿Causaría esto que la mangosta encuentre todos los registros antes de aplicar las condiciones?
FluffyBeing

37

Puede usar un pequeño paquete llamado Paginate de mangosta que lo hace más fácil.

$ npm install mongoose-paginate

Después de en sus rutas o controlador, simplemente agregue:

/**
 * 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);
  }
}

2
¿Está optimizado?
Argento

16

Este es un ejemplo de ejemplo, puedes probar esto,

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
      });
  });
 });

11

Intente usar la función mangosta para la paginación. El límite es el número de registros por página y el número de la página.

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){
 });

10

Esto es lo que hice en el código

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
    });

Así es como lo hice.


1
Cómo obtener el número total de páginas
Rhushikesh

Hola @Rhushikesh, puedes usar una función count () para obtener el conteo. Pero parece que debe ser otra consulta de la base de datos. Detalles aquí mongoosejs.com/docs/api.html#model_Model.count
Indra Santosa

@Rhushikesh consigue el recuento y divídelo por el límite
edthethird

count()es obsoleto. usocountDocuments()
Ruslan

7

Consulta;
búsqueda = productName,

Params;
página = 1

// Pagination
router.get("/search/:page", (req, res, next) => {
  const resultsPerPage = 5;
  const page = req.params.page >= 1 ? req.params.page : 1;
  const query = req.query.search;

  Product.find({ name: query })
    .select("name")
    .sort({ name: "asc" })
    .limit(resultsPerPage)
    .skip(resultsPerPage * page)
    .then((results) => {
      return res.status(200).send(results);
    })
    .catch((err) => {
      return res.status(500).send(err);
    });
});

Gracias por esta respuesta Lo intenté primero después de leer el hilo porque era uno de los más recientes. Sin embargo, cuando lo implementé, descubrí un error: como está escrito ahora, nunca devolverá la primera página de resultados, ya que SIEMPRE tendrá un valor de omisión. Intente agregar "page = page-1" antes de la llamada Product.find ().
Interog

6

Aquí hay una versión que adjunto a todos mis modelos. Depende del guión bajo para mayor comodidad y asíncrono para el rendimiento. La opción permite la selección y clasificación de campos utilizando la sintaxis de mangosta.

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;
}

Adjúntelo a su esquema de modelo.

MySchema.statics.findPaginated = findPaginated;

6

Solución de paginación simple y potente.

async getNextDocs(no_of_docs_required: number, last_doc_id?: string) {
    let docs

    if (!last_doc_id) {
        // get first 5 docs
        docs = await MySchema.find().sort({ _id: -1 }).limit(no_of_docs_required)
    }
    else {
        // get next 5 docs according to that last document id
        docs = await MySchema.find({_id: {$lt: last_doc_id}})
                                    .sort({ _id: -1 }).limit(no_of_docs_required)
    }
    return docs
}

last_doc_id: la última identificación de documento que obtienes

no_of_docs_required: el número de documentos que desea recuperar, es decir, 5, 10, 50, etc.

  1. Si no proporciona el last_doc_idmétodo, obtendrá 5 documentos más recientes.
  2. Si ha proporcionado el last_doc_id, obtendrá los siguientes, es decir, 5 documentos.

5

La respuesta anterior es válida.

¡Solo un complemento para cualquiera que esté en espera asincrónica en lugar de prometer!

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
        });
    }
};

4

puedes usar la siguiente línea de código también

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()

este código funcionará en la última versión de mongo


3

Un enfoque sólido para implementar esto sería pasar los valores de la interfaz utilizando una cadena de consulta . Digamos que queremos obtener la página 2 y también limitar el resultado a 25 resultados .
La cadena de consulta se vería así:?page=2&limit=25 // this would be added onto your URL: http:localhost:5000?page=2&limit=25

Veamos el código:

// We would receive the values with req.query.<<valueName>>  => e.g. req.query.page
// Since it would be a String we need to convert it to a Number in order to do our
// necessary calculations. Let's do it using the parseInt() method and let's also provide some default values:

  const page = parseInt(req.query.page, 10) || 1; // getting the 'page' value
  const limit = parseInt(req.query.limit, 10) || 25; // getting the 'limit' value
  const startIndex = (page - 1) * limit; // this is how we would calculate the start index aka the SKIP value
  const endIndex = page * limit; // this is how we would calculate the end index

// We also need the 'total' and we can get it easily using the Mongoose built-in **countDocuments** method
  const total = await <<modelName>>.countDocuments();

// skip() will return a certain number of results after a certain number of documents.
// limit() is used to specify the maximum number of results to be returned.

// Let's assume that both are set (if that's not the case, the default value will be used for)

  query = query.skip(startIndex).limit(limit);

  // Executing the query
  const results = await query;

  // Pagination result 
 // Let's now prepare an object for the frontend
  const pagination = {};

// If the endIndex is smaller than the total number of documents, we have a next page
  if (endIndex < total) {
    pagination.next = {
      page: page + 1,
      limit
    };
  }

// If the startIndex is greater than 0, we have a previous page
  if (startIndex > 0) {
    pagination.prev = {
      page: page - 1,
      limit
    };
  }

 // Implementing some final touches and making a successful response (Express.js)

const advancedResults = {
    success: true,
    count: results.length,
    pagination,
    data: results
 }
// That's it. All we have to do now is send the `results` to the frontend.
 res.status(200).json(advancedResults);

Sugeriría implementar esta lógica en middleware para que pueda usarla en varias rutas / controladores.


2

La forma más fácil y rápida es paginar con el ejemplo objectId;

Condición de carga inicial

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

Tome el primer y último ObjectId de los datos de respuesta

Página siguiente condición

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

Página siguiente condición

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

En mangosta

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

El mejor enfoque (IMO) es usar saltar y limitar PERO dentro de colecciones o documentos limitados.

Para realizar la consulta dentro de documentos limitados, podemos usar un índice específico como índice en un campo de tipo FECHA. Mira eso a continuación

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

El complemento más fácil para la paginación.

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

Agregue el complemento a un esquema y luego use el método de paginación modelo:

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

este complemento está roto con mangosta v5.5.5
Isaac Pak

1

Esta es una función de ejemplo para obtener el resultado del modelo de habilidades con opciones de paginación y límite

 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

}


0

Puede escribir una consulta como esta.

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);
        }
    });

página: número de página que proviene del cliente como parámetros de solicitud.
por página: no se muestran resultados por página

Si está utilizando MEAN stack, la siguiente publicación de blog proporciona gran parte de la información para crear paginación en el front-end usando bootstrap de interfaz de usuario angular y usando métodos de salto y límite de mangosta en el back-end.

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


0

Puede usar skip () y limit (), pero es muy ineficiente. Una mejor solución sería ordenar en campo indexado más limit (). Nosotros en Wunderflats hemos publicado una pequeña biblioteca aquí: https://github.com/wunderflats/goosepage Utiliza la primera manera.


0

Si está usando mangosta como fuente de una API relajante, eche un vistazo a ' restify-mangosta ' y sus consultas. Tiene exactamente esta funcionalidad incorporada.

Cualquier consulta en una colección proporciona encabezados que son útiles aquí

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

Entonces, básicamente, obtienes un servidor genérico con un tiempo de carga relativamente lineal para consultas a colecciones. Eso es increíble y algo a tener en cuenta si quieres entrar en una implementación propia.


0
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
**//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

Usa este simple complemento.

https://github.com/WebGangster/mongoose-paginate-v2

Instalación

npm install mongoose-paginate-v2
Uso Agregue el complemento a un esquema y luego use el método de paginación modelo:

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

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

mySchema.plugin(mongoosePaginate);

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

myModel.paginate().then({}) // Usage


Este complemento ya ha sido "sugerido" en otra respuesta. También sería útil saber que usted es un contribuyente en este paquete.
lukas_o

@lukas_o Sí. Soy el creador del complemento.
Aravind NC

0

de acuerdo a

Chris Hinkle

responder:

//assume every page has 50 result
const results = (req.query.page * 1) * 50;
MyModel.find( { fieldNumber: { $lte: results} })
.limit( 50 )
.sort( '+fieldNumber' )

//one thing left is create a fieldNumber on the schema thas holds ducument number

0

Usando ts-mangosta-paginación

    const trainers = await Trainer.paginate(
        { user: req.userId },
        {
            perPage: 3,
            page: 1,
            select: '-password, -createdAt -updatedAt -__v',
            sort: { createdAt: -1 },
        }
    )

    return res.status(200).json(trainers)

0
let page,limit,skip,lastPage, query;
 page = req.params.page *1 || 1;  //This is the page,fetch from the server
 limit = req.params.limit * 1 || 1; //  This is the limit ,it also fetch from the server
 skip = (page - 1) * limit;   // Number of skip document
 lastPage = page * limit;   //last index 
 counts = await userModel.countDocuments() //Number of document in the collection

query = query.skip(skip).limit(limit) //current page

const paginate = {}

//For previous page
if(skip > 0) {
   paginate.prev = {
       page: page - 1,
       limit: limit
} 
//For next page
 if(lastPage < counts) {
  paginate.next = {
     page: page + 1,
     limit: limit
}
results = await query //Here is the final results of the query.

-1

También pude lograr resultados con async / wait.

Ejemplo de código a continuación utilizando un controlador asíncrono con hapi v17 y 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;
               }

            }
        }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.