Querying
Monch provides a chainable cursor API for building queries with full type safety.
Basic Queries
// Find all
const users = await Users.find().toArray();
// Find with filter
const admins = await Users.find({ role: 'admin' }).toArray();
// Find one
const user = await Users.findOne({ email: 'alice@example.com' });
// Find by ID (accepts string or ObjectId)
const user = await Users.findById('507f1f77bcf86cd799439011');Chainable Cursor
The find() method returns a Cursor that supports method chaining:
const users = await Users.find({ status: 'active' })
.sort({ createdAt: -1 })
.skip(20)
.limit(10)
.project({ name: 1, email: 1 })
.toArray();sort
Sort results by one or more fields:
// Single field
.sort({ createdAt: -1 }) // Descending
// Multiple fields
.sort({ role: 1, name: 1 }) // Ascendingskip
Skip a number of documents:
.skip(20) // Skip first 20 documentslimit
Limit the number of results:
.limit(10) // Return at most 10 documentsproject
Select specific fields:
// Include fields
.project({ name: 1, email: 1 })
// Exclude fields
.project({ password: 0, secret: 0 })Executing Queries
toArray
Execute and return results as an array:
const users = await Users.find({ role: 'admin' }).toArray();
// Returns MonchArray<User> (array with .serialize() method)The returned array has a .serialize() method for converting all documents:
const users = await Users.find({ role: 'admin' }).toArray();
const plain = users.serialize();
// All documents serialized for JSON/Next.jsserialize
Execute and serialize in one step:
const users = await Users.find({ role: 'admin' }).serialize();
// Returns Serialized<User>[] - ready for JSON/Next.jsThis is equivalent to .toArray() followed by .serialize(), but more efficient.
paginate
Execute with pagination metadata:
const result = await Users.find({ status: 'active' })
.sort({ createdAt: -1 })
.paginate({ page: 2, limit: 20 });Returns:
{
data: User[], // Array of documents
pagination: {
page: number, // Current page (1-based)
limit: number, // Items per page
total: number, // Total matching documents
totalPages: number, // Total number of pages
hasNext: boolean, // Has more pages
hasPrev: boolean, // Has previous pages
}
}Options:
page- Page number (default: 1)limit- Items per page (default: 20, silently capped at 100)
MongoDB Query Operators
Monch supports all MongoDB query operators:
Comparison
// Equal
{ age: 25 }
{ age: { $eq: 25 } }
// Not equal
{ status: { $ne: 'deleted' } }
// Greater than / less than
{ age: { $gt: 18 } }
{ age: { $gte: 18 } }
{ age: { $lt: 65 } }
{ age: { $lte: 65 } }
// In array
{ role: { $in: ['admin', 'moderator'] } }
{ role: { $nin: ['banned', 'suspended'] } }Logical
// AND (implicit)
{ role: 'admin', status: 'active' }
// AND (explicit)
{ $and: [{ role: 'admin' }, { status: 'active' }] }
// OR
{ $or: [{ role: 'admin' }, { isOwner: true }] }
// NOR
{ $nor: [{ status: 'deleted' }, { status: 'banned' }] }
// NOT
{ age: { $not: { $lt: 18 } } }Element
// Field exists
{ deletedAt: { $exists: false } }
// Type check
{ age: { $type: 'number' } }Array
// Contains element
{ tags: 'javascript' }
// Contains all elements
{ tags: { $all: ['javascript', 'typescript'] } }
// Array size
{ tags: { $size: 3 } }
// Element match
{ comments: { $elemMatch: { author: 'Alice', score: { $gt: 5 } } } }Text Search
// Requires text index
{ $text: { $search: 'mongodb tutorial' } }Regex
// Case-insensitive search
{ name: { $regex: /alice/i } }
{ name: { $regex: 'alice', $options: 'i' } }Nested Fields
Query nested fields using dot notation:
// Nested object field
{ 'profile.bio': { $exists: true } }
{ 'address.city': 'New York' }
// Array element field
{ 'comments.0.author': 'Alice' }
// Any array element
{ 'comments.author': 'Alice' }MonchFilter Type
Monch uses a relaxed filter type (MonchFilter<T>) that works better with Zod-inferred types:
// All of these work without type casting:
Users.find({ email: 'test@example.com' }); // Known field
Users.find({ createdAt: { $gte: new Date() } }); // Timestamp field
Users.find({ 'profile.name': 'John' }); // Nested path
Users.find({ $or: [{ status: 'active' }, { role: 'admin' }] });You typically don't need to import MonchFilter directly—all collection methods use it internally.
Performance Tips
- Use indexes for frequently queried fields
- Limit results to avoid loading too much data
- Project only needed fields to reduce transfer size
- Use
exists()instead ofcount() > 0for existence checks - Use
estimatedDocumentCount()when you don't need exact counts