Mongoose Paginate для JavaScript (Node.js): полное руководство с примерами
Добро пожаловать на PythonLib! Сегодня мы разберем Mongoose Paginate — одну из самых популярных библиотек для реализации пагинации в приложениях на Node.js, использующих Mongoose ODM (Object Data Modeling) для MongoDB.
1. Что это и зачем нужна
Mongoose Paginate — это легковесный плагин для Mongoose, который добавляет метод paginate() к вашим схемам. Он позволяет легко разбивать большие наборы данных на страницы, избегая загрузки всех документов сразу.
Зачем это нужно? В реальных приложениях (блоги, интернет-магазины, API) базы данных содержат тысячи, а иногда и миллионы записей. Загружать их все одновременно — это:
- Огромная нагрузка на сервер (память, процессор).
- Медленная работа API (пользователь ждет ответа минутами).
- Плохой UX (бесконечный скролл без пагинации или кнопок "Далее").
Mongoose Paginate решает эти проблемы, предоставляя готовую, протестированную и гибкую реализацию пагинации. Она автоматически считает общее количество документов, возвращает нужную страницу и предоставляет мета-информацию (текущая страница, всего страниц, есть ли следующая/предыдущая).
2. Установка
Установка производится через npm (Node Package Manager). Убедитесь, что у вас уже установлен Node.js и npm.
npm install mongoose-paginate-v2
Важно: Существует две версии библиотеки:
mongoose-paginate— старая, менее поддерживаемая версия.mongoose-paginate-v2— актуальная, рекомендуемая версия с большим количеством возможностей и исправлений.
В этой статье мы будем использовать mongoose-paginate-v2.
3. Быстрый старт — минимальный рабочий пример
Давайте создадим простую модель "Пользователь" и применим к ней пагинацию.
// 1. Подключаем зависимости
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');
// 2. Определяем схему
const userSchema = new mongoose.Schema({
name: String,
email: String,
age: Number,
createdAt: { type: Date, default: Date.now }
});
// 3. Добавляем плагин пагинации к схеме
userSchema.plugin(mongoosePaginate);
// 4. Создаем модель
const User = mongoose.model('User', userSchema);
// 5. Подключаемся к MongoDB (пример с локальной БД)
async function main() {
await mongoose.connect('mongodb://localhost:27017/testdb');
// 6. Вставляем тестовые данные (если их нет)
const count = await User.countDocuments();
if (count === 0) {
const users = [];
for (let i = 1; i <= 50; i++) {
users.push({ name: `User ${i}`, email: `user${i}@example.com`, age: 20 + (i % 30) });
}
await User.insertMany(users);
console.log('Вставлено 50 пользователей');
}
// 7. Используем пагинацию
const options = {
page: 1, // Номер страницы (по умолчанию 1)
limit: 10, // Количество элементов на странице (по умолчанию 10)
};
const result = await User.paginate({}, options);
console.log('Результат пагинации:');
console.log('Документы:', result.docs);
console.log('Всего документов:', result.totalDocs);
console.log('Всего страниц:', result.totalPages);
console.log('Текущая страница:', result.page);
console.log('Есть следующая страница:', result.hasNextPage);
console.log('Есть предыдущая страница:', result.hasPrevPage);
await mongoose.disconnect();
}
main().catch(console.error);
Этот код создаст 50 пользователей, а затем запросит первую страницу с 10 пользователями. В консоли вы увидите не только сами документы, но и мета-информацию.
4. Основные методы/функции/классы
Библиотека mongoose-paginate-v2 предоставляет один главный метод — paginate(), но его гибкость достигается за счет множества опций. Рассмотрим ключевые из них.
4.1. Model.paginate([query], [options], [callback])
Сигнатура: Model.paginate(query, options, callback)
Что делает: Основной метод для выполнения пагинированного запроса. Принимает фильтр (query), опции пагинации и опциональный callback. Если callback не передан, возвращает Promise.
const result = await User.paginate({ age: { $gte: 25 } }, { page: 2, limit: 5 });
4.2. Опция page
Сигнатура: { page: Number }
Что делает: Определяет номер запрашиваемой страницы. Нумерация начинается с 1. По умолчанию: 1.
const result = await User.paginate({}, { page: 3 }); // Получить 3-ю страницу
4.3. Опция limit
Сигнатура: { limit: Number }
Что делает: Определяет максимальное количество документов на одной странице. По умолчанию: 10.
const result = await User.paginate({}, { limit: 25 }); // 25 пользователей на странице
4.4. Опция sort
Сигнатура: { sort: Object | String }
Что делает: Определяет порядок сортировки документов. Работает так же, как и в обычном find().sort().
const result = await User.paginate({}, {
sort: { createdAt: -1 } // Сортировка по дате создания (новые сверху)
});
4.5. Опция select
Сигнатура: { select: Object | String }
Что делает: Позволяет выбрать только определенные поля документа. Экономит трафик и ускоряет запросы.
const result = await User.paginate({}, {
select: 'name email' // Вернуть только name и email
});
4.6. Опция populate
Сигнатура: { populate: Object | Array }
Что делает: Позволяет "развернуть" ссылки (refs) на другие коллекции, аналогично методу .populate() в Mongoose. Может быть массивом для множественных populate.
// Предположим, у пользователя есть поле 'posts', ссылающееся на модель Post
const result = await User.paginate({}, {
populate: { path: 'posts', select: 'title' }
});
4.7. Опция lean
Сигнатура: { lean: Boolean }
Что делает: Если установлено в true, возвращает простые JavaScript-объекты вместо полноценных документов Mongoose. Это значительно ускоряет запросы, но вы теряете методы Mongoose (например, .save()). Рекомендуется для read-only API.
const result = await User.paginate({}, { lean: true }); // Быстрее, но без методов Mongoose
4.8. Опция customLabels
Сигнатура: { customLabels: Object }
Что делает: Позволяет переименовать поля в возвращаемом результате. Полезно, если ваш фронтенд ожидает другие названия (например, items вместо docs).
const myCustomLabels = {
totalDocs: 'itemCount',
docs: 'items',
page: 'currentPage',
nextPage: 'next',
prevPage: 'prev',
totalPages: 'pageCount',
hasNextPage: 'hasNext',
hasPrevPage: 'hasPrev'
};
const result = await User.paginate({}, { customLabels: myCustomLabels });
console.log(result.items); // Вместо result.docs
console.log(result.itemCount); // Вместо result.totalDocs
5. Реальный практический пример (100-200 строк кода)
Давайте создадим полноценный REST API для блога с использованием Express, Mongoose и Mongoose Paginate.
// server.js
const express = require('express');
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');
const app = express();
app.use(express.json());
// --- Модель ---
const postSchema = new mongoose.Schema({
title: { type: String, required: true },
content: { type: String, required: true },
author: { type: String, required: true },
tags: [String],
published: { type: Boolean, default: false },
createdAt: { type: Date, default: Date.now }
});
postSchema.plugin(mongoosePaginate);
const Post = mongoose.model('Post', postSchema);
// --- Подключение к БД ---
mongoose.connect('mongodb://localhost:27017/blogdb')
.then(() => console.log('MongoDB подключена'))
.catch(err => console.error('Ошибка подключения:', err));
// --- Эндпоинт для создания поста ---
app.post('/api/posts', async (req, res) => {
try {
const post = new Post(req.body);
await post.save();
res.status(201).json(post);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// --- Эндпоинт для получения постов с пагинацией ---
app.get('/api/posts', async (req, res) => {
try {
// Параметры из query-строки с значениями по умолчанию
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const sort = req.query.sort || '-createdAt'; // По умолчанию новые сверху
const author = req.query.author;
const tag = req.query.tag;
const published = req.query.published;
// Строим фильтр
const filter = {};
if (author) filter.author = author;
if (tag) filter.tags = { $in: [tag] };
if (published !== undefined) filter.published = published === 'true';
// Опции пагинации
const options = {
page,
limit,
sort,
lean: true, // Для read-only API это быстрее
select: 'title author tags published createdAt', // Не возвращаем content для списка
customLabels: {
docs: 'posts',
totalDocs: 'totalPosts',
page: 'currentPage',
totalPages: 'totalPages'
}
};
const result = await Post.paginate(filter, options);
res.json({
success: true,
...result,
// Добавляем удобные ссылки для API
links: {
self: `/api/posts?page=${result.currentPage}&limit=${limit}`,
next: result.hasNextPage ? `/api/posts?page=${result.nextPage}&limit=${limit}` : null,
prev: result.hasPrevPage ? `/api/posts?page=${result.prevPage}&limit=${limit}` : null
}
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// --- Эндпоинт для получения одного поста (без пагинации) ---
app.get('/api/posts/:id', async (req, res) => {
try {
const post = await Post.findById(req.params.id);
if (!post) return res.status(404).json({ error: 'Пост не найден' });
res.json(post);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// --- Запуск сервера ---
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Сервер запущен на порту ${PORT}`);
// Добавим тестовые данные при первом запуске
seedData();
});
async function seedData() {
const count = await Post.countDocuments();
if (count === 0) {
const posts = [];
const authors = ['Alice', 'Bob', 'Charlie'];
const tags = ['javascript', 'nodejs', 'mongodb', 'tutorial', 'api'];
for (let i = 1; i <= 100; i++) {
posts.push({
title: `Post ${i}: How to use ${tags[i % tags.length]}`,
content: `This is the content of post ${i}. It contains a lot of useful information about ${tags[i % tags.length]}.`,
author: authors[i % authors.length],
tags: [tags[i % tags.length], tags[(i + 1) % tags.length]],
published: i % 3 !== 0 // 2/3 постов опубликованы
});
}
await Post.insertMany(posts);
console.log('Добавлено 100 тестовых постов');
}
}
Как использовать:
- Запустите сервер:
node server.js - Создайте пост:
POST /api/postsс JSON-телом. - Получите список постов:
GET /api/posts?page=2&limit=5&author=Alice - Попробуйте фильтры:
GET /api/posts?tag=mongodb&published=true
6. Советы по производительности и типичные ошибки
Советы по производительности:
- Используйте
lean: trueдля read-only запросов. Это уменьшает накладные расходы на создание полноценных документов Mongoose. - Ограничивайте
Также в библиотеке
Работа с CSV в PHP: чтение, запись и обработка данных
Мониторинг Node.js: инструменты и библиотеки для observability
Планировщик задач на TypeScript: подробное руководство по библиотеке node-schedule
AngleSharp для C# (.NET): полное руководство с примерами
Swagger/OpenAPI генерация для JavaScript (Node.js) — инструменты и библиотеки