Node.js中Mongoose与Handlebars的分页实现求助
Hey there! Let's break this down step by step—first handling the pagination logic with Mongoose, then hooking it up to your Handlebars frontend. I've worked through this exact setup plenty of times, so I'll make it straightforward for you.
You've got two solid options here: using Mongoose's built-in methods, or leveraging a pagination plugin for less boilerplate.
Option 1: Native Mongoose Methods (No Plugins)
This is great if you want full control over the logic.
First, define constants for how many posts you want per page, then grab the current page from the request query parameters:
// In your route handler (e.g., /posts) const ITEMS_PER_PAGE = 10; // Adjust this to your preference const page = parseInt(req.query.page) || 1; // Default to page 1 if no page is specified const skip = (page - 1) * ITEMS_PER_PAGE; // Calculate how many posts to skip // Fetch the paginated posts (sorted by newest first here) const posts = await Post.find() .sort({ createdAt: -1 }) .skip(skip) .limit(ITEMS_PER_PAGE); // Get total number of posts to calculate total pages const totalPosts = await Post.countDocuments(); const totalPages = Math.ceil(totalPosts / ITEMS_PER_PAGE); // Pass data to your Handlebars template res.render('posts', { posts, currentPage: page, totalPages });
Option 2: Use the mongoose-paginate-v2 Plugin
This plugin simplifies the process by wrapping the pagination logic into a single method.
- Install it first:
npm install mongoose-paginate-v2
- Add it to your Post model:
const mongoose = require('mongoose'); const mongoosePaginate = require('mongoose-paginate-v2'); const postSchema = new mongoose.Schema({ title: String, content: String, // ... other fields }); postSchema.plugin(mongoosePaginate); module.exports = mongoose.model('Post', postSchema);
- Use it in your route handler:
const ITEMS_PER_PAGE = 10; const page = parseInt(req.query.page) || 1; const result = await Post.paginate({}, { page: page, limit: ITEMS_PER_PAGE, sort: { createdAt: -1 } }); // The result object includes: // - docs: your paginated posts // - totalPages: total number of pages // - page: current page number res.render('posts', { posts: result.docs, currentPage: result.page, totalPages: result.totalPages });
Now that your backend is sending the right data, let's render the posts and pagination controls.
First, render your list of posts:
{{!-- posts.handlebars template --}} <div class="posts-container"> {{#each posts}} <div class="post-card"> <h2>{{title}}</h2> <p>{{content}}</p> <a href="/posts/{{_id}}">Read More</a> </div> {{else}} <p>No posts found!</p> {{/each}} </div>
Next, add the pagination controls. Handlebars doesn't have built-in helpers for math or comparisons, so we'll register some custom helpers first in your Express setup:
const express = require('express'); const { engine } = require('express-handlebars'); const app = express(); // Register custom Handlebars helpers app.engine('handlebars', engine({ helpers: { // Math helpers add: (a, b) => a + b, subtract: (a, b) => a - b, // Comparison helpers gt: (a, b) => a > b, lt: (a, b) => a < b, // Generate a range of page numbers to display (avoids showing 100+ page buttons) getPageRange: (currentPage, totalPages) => { const range = []; const start = Math.max(1, currentPage - 3); // Show up to 3 pages before current const end = Math.min(totalPages, currentPage + 3); // Show up to 3 pages after current for (let i = start; i <= end; i++) { range.push(i); } return range; } } })); app.set('view engine', 'handlebars');
Now add the pagination HTML to your template:
<div class="pagination"> {{!-- Previous Page Button --}} {{#if (gt currentPage 1)}} <a href="/posts?page={{subtract currentPage 1}}" class="pagination-btn">← Previous</a> {{/if}} {{!-- Page Number Buttons --}} {{#each getPageRange currentPage totalPages}} {{#if (eq this currentPage)}} <span class="pagination-btn active">{{this}}</span> {{else}} <a href="/posts?page={{this}}" class="pagination-btn">{{this}}</a> {{/if}} {{/each}} {{!-- Next Page Button --}} {{#if (lt currentPage totalPages)}} <a href="/posts?page={{add currentPage 1}}" class="pagination-btn">Next →</a> {{/if}} </div>
A quick edge case tip: if totalPages is 1, you might want to hide the pagination controls entirely. Add this check to wrap the pagination code:
{{#if (gt totalPages 1)}} {{!-- Render pagination controls here --}} {{/if}}
内容的提问来源于stack exchange,提问作者Gabriel




