如何在Node+MongoDB中实现带分页的多条件搜索API端点
Great start on your search endpoint! Let's fix the bottlenecks and add the pagination/limit features you need. Here's a step-by-step breakdown and improved implementation:
First, Let's Address the Issues in Your Current Code
- When no search parameters (
fn/ln/cp) are provided, your empty$orarray will return all Profile documents (probably not what you want). - You're using
Profile.find()with a callback incorrectly—Mongoose returns a Query object here, not the actual results, and yourawaitusage is causing redundant database queries. - Error handling doesn't respect custom status codes (you're defaulting all errors to 500, even 404 cases).
Improved Search Controller Implementation
// Models const { Profile } = require("../models"); // Error handling const { ErrorHandlers } = require("../utilities"); const SearchController = { async getQuery(req, res) { try { // Destructure query params with default values for pagination const { fn, ln, cp, page = 1, limit = 10 } = req.query; // 1. Block empty search requests if (!fn && !ln && !cp) { throw new ErrorHandlers.ErrorHandler( 400, "Please provide at least one search parameter: fn (firstname), ln (surname), or cp (company)" ); } // 2. Build dynamic query conditions const queryConditions = []; if (fn) { // Use exact match as requested; swap to regex below for partial/caseless search queryConditions.push({ firstname: fn }); } if (ln) { queryConditions.push({ surname: ln }); } if (cp) { // Match profiles where ANY experience entry has this company queryConditions.push({ "experience.company": cp }); } const query = { $or: queryConditions }; // 3. Parse pagination values (query params are strings by default) const pageNum = parseInt(page); const limitNum = parseInt(limit); const skip = (pageNum - 1) * limitNum; // 4. Execute query with pagination const searchResult = await Profile.find(query) .skip(skip) .limit(limitNum); // 5. Handle no matching results if (searchResult.length === 0) { throw new ErrorHandlers.ErrorHandler( 404, "No profiles match your search criteria" ); } // 6. Return results with helpful pagination metadata res.status(200).json({ page: pageNum, limit: limitNum, totalResults: searchResult.length, data: searchResult }); } catch (err) { // Use custom error status code if available, else default to 500 res.status(err.statusCode || 500).json({ message: err.message }); } } }; module.exports = SearchController;
Key Enhancements Explained
1. Empty Query Protection
We immediately return a 400 error if no search parameters are provided, preventing accidental full-database scans.
2. Flexible Pagination
pageandlimitare optional query params with sensible defaults (page 1, 10 results per page)skipcalculates how many documents to skip to reach the requested page- Chain
.skip()and.limit()to the Mongoose query to apply pagination efficiently
3. Optional: Partial/Case-Insensitive Search
If you want to allow partial matches (e.g., fn=jo finds "Joe" and "Joseph") or case-insensitive searches, replace the exact match lines with regex:
// For case-insensitive partial match on firstname queryConditions.push({ firstname: { $regex: fn, $options: 'i' } });
4. Better Error Handling
We now respect the status code from your custom ErrorHandler, so 400/404 errors return the correct HTTP status instead of defaulting to 500.
5. Metadata for Frontend
Including page, limit, and totalResults in the response makes it easier for frontend developers to build pagination controls. If you want the total number of matching documents (not just the current page count), add a separate count query:
const totalMatching = await Profile.countDocuments(query); // Then add totalMatching to your response JSON
内容的提问来源于stack exchange,提问作者Jakub

