mongoose-query-find
v2.1.1
Published
QueryFind is a TypeScript utility for Mongoose that simplifies building dynamic queries. It supports advanced filtering, global search across multiple fields, sorting, field selection, and pagination for MongoDB documents, making API query handling more e
Maintainers
Readme
mongoose-query-find
A fluent, chainable query builder for Mongoose that handles filtering, global search, sorting, field projection, population, and pagination — all driven directly from URL query parameters with zero boilerplate.
Table of Contents
- Installation
- Quick Start
- Constructor
- Builder Methods
- Terminal Method
- Query Parameter Reference
- Full Example (Express)
- TypeScript Types
- Links
- License
Installation
npm install mongoose-query-findyarn add mongoose-query-findpnpm add mongoose-query-findPeer dependency: requires
mongoose ^8ormongoose ^9installed in your project.
Quick Start
import QueryFind from 'mongoose-query-find';
import UserModel from './models/user';
const result = await new QueryFind(UserModel.find(), req.query)
.filter()
.globalFilter(['name', 'email'])
.sort()
.limitFields('-password -__v')
.paginate();result will look like:
{
"data": [...],
"total": 84,
"page": 2,
"totalPages": 9,
"limit": 10
}Constructor
new QueryFind(query, queryString);| Parameter | Type | Description |
| ------------- | ----------------------------------- | --------------------------------------------- |
| query | Query<TRawDocType[], TRawDocType> | A Mongoose query, e.g. Model.find() |
| queryString | QueryParams | The parsed URL query object, e.g. req.query |
Builder Methods
All builder methods return this, so they are fully chainable in any order.
.filter()
Parses the URL query string into a Mongoose filter. Automatically:
- Strips reserved keys (
page,limit,sort,fields,q) - Converts comparison operator names to MongoDB
$syntax (gt→$gt,lte→$lte, etc.) - Coerces string booleans to real booleans (
"true"→true,"false"→false) - Coerces date-like string values to
Dateinstances for fields namedcreatedAt,updatedAt,deletedAt,date, orDate
Supported operators: eq, ne, gt, gte, lt, lte, in, nin
GET /users?age[gte]=18&isActive=true&role=admin.filter()
// → { age: { $gte: 18 }, isActive: true, role: 'admin' }.globalFilter(fields: string[])
Adds a case-insensitive $or regex search across the specified fields when the q query parameter is present. If q is absent, this method is a no-op.
GET /users?q=john.globalFilter(['name', 'email'])
// → { $or: [{ name: /john/i }, { email: /john/i }] }.sort()
Applies sort order from the sort query parameter. Prefix a field with - for descending order. Multiple fields are comma-separated.
GET /users?sort=-createdAt,name.sort()
// → sorts by createdAt DESC, then name ASCDefaults to { createdAt: -1 } when the sort param is absent.
.limitFields(defaultFields?: string)
Controls which fields are returned (projection). Uses the fields query param when present, otherwise falls back to defaultFields.
Priority order:
?fields=query param — always wins when presentdefaultFieldsargument — used as fallback when no query param- No projection at all when both are absent (all fields returned)
GET /users?fields=name,email,role.limitFields('-password -__v')
// With ?fields=name,email,role → selects only name, email, role
// Without ?fields → excludes password and __v.populate(path: string | PopulateOptions, select?: string)
Registers a populate directive. Can be called multiple times to populate multiple paths — each call appends to the internal list. All registered populates are applied inside paginate() after the find query is built, so core filter / sort / pagination logic is completely untouched.
Accepts the same arguments as Mongoose's own .populate():
// Plain path string
.populate('author')
// Path + select string
.populate('author', 'name email')
// Full PopulateOptions object
.populate({ path: 'comments', select: 'text createdAt', match: { visible: true } })
// Multiple calls — each appends to the list
.populate('author')
.populate({ path: 'comments', select: 'text createdAt' })Terminal Method
.paginate()
Executes the query and returns a Promise<PaginatedResult<T>>. Makes exactly two database round-trips:
countDocuments— counts total matching documents (uses index scan)find— fetches the requested page with sort, skip, limit, projection, and any registered populates applied
GET /users?page=2&limit=20Returns:
{
data: T[]; // Documents for the current page
total: number; // Total matching documents across all pages
page: number; // Current page (auto-corrects to 1 if out of range)
totalPages: number;
limit: number;
}If the requested
pageexceedstotalPages(e.g. after a deletion), page1is returned automatically so the caller always receives valid data.
Defaults: page=1, limit=10
Query Parameter Reference
| Parameter | Example | Description |
| ----------- | ------------------------- | ------------------------------------------------- |
| page | ?page=3 | Page number (default: 1, min: 1) |
| limit | ?limit=25 | Documents per page (default: 10, min: 1) |
| sort | ?sort=-createdAt,name | Sort fields; prefix - for descending |
| fields | ?fields=name,email | Comma-separated fields to include in the response |
| q | ?q=john | Global search term (used by .globalFilter()) |
| (any key) | ?role=admin&age[gte]=18 | Field-level filters processed by .filter() |
Full Example (Express)
import { Request, Response } from 'express';
import QueryFind from 'mongoose-query-find';
import UserModel from '../models/user';
export const getUsers = async (req: Request, res: Response) => {
const result = await new QueryFind(UserModel.find(), req.query)
.filter()
.globalFilter(['name', 'email', 'username'])
.sort()
.limitFields('-password -__v')
.populate('role', 'name permissions')
.paginate();
res.json({
status: 'success',
...result,
});
};Example requests:
# Page 2, 15 per page, only active admins sorted by name
GET /users?page=2&limit=15&role=admin&isActive=true&sort=name
# Search "alice" across name, email, and username
GET /users?q=alice
# Users older than 25, return only name and email
GET /users?age[gt]=25&fields=name,email
# Filter by date range
GET /users?createdAt[$gte]=2024-01-01&createdAt[$lte]=2024-12-31
# Combined: search + filter + sort + pagination
GET /users?q=john&role=editor&sort=-createdAt&page=1&limit=5TypeScript Types
Both types are exported and available for use in your own code:
import QueryFind, { QueryParams, PaginatedResult } from 'mongoose-query-find';interface QueryParams {
page?: string;
limit?: string;
sort?: string;
fields?: string;
q?: string;
[key: string]: unknown;
}
interface PaginatedResult<T> {
data: T[];
total: number;
page: number;
totalPages: number;
limit: number;
}Links
License
ISC © [email protected]
