Skip to content

Querying

Monch provides a chainable cursor API for building queries with full type safety.

Basic Queries

typescript
// 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:

typescript
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:

typescript
// Single field
.sort({ createdAt: -1 })  // Descending

// Multiple fields
.sort({ role: 1, name: 1 })  // Ascending

skip

Skip a number of documents:

typescript
.skip(20)  // Skip first 20 documents

limit

Limit the number of results:

typescript
.limit(10)  // Return at most 10 documents

project

Select specific fields:

typescript
// Include fields
.project({ name: 1, email: 1 })

// Exclude fields
.project({ password: 0, secret: 0 })

Executing Queries

toArray

Execute and return results as an array:

typescript
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:

typescript
const users = await Users.find({ role: 'admin' }).toArray();
const plain = users.serialize();
// All documents serialized for JSON/Next.js

serialize

Execute and serialize in one step:

typescript
const users = await Users.find({ role: 'admin' }).serialize();
// Returns Serialized<User>[] - ready for JSON/Next.js

This is equivalent to .toArray() followed by .serialize(), but more efficient.

paginate

Execute with pagination metadata:

typescript
const result = await Users.find({ status: 'active' })
  .sort({ createdAt: -1 })
  .paginate({ page: 2, limit: 20 });

Returns:

typescript
{
  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

typescript
// 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

typescript
// 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

typescript
// Field exists
{ deletedAt: { $exists: false } }

// Type check
{ age: { $type: 'number' } }

Array

typescript
// 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 } } } }
typescript
// Requires text index
{ $text: { $search: 'mongodb tutorial' } }

Regex

typescript
// Case-insensitive search
{ name: { $regex: /alice/i } }
{ name: { $regex: 'alice', $options: 'i' } }

Nested Fields

Query nested fields using dot notation:

typescript
// 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:

typescript
// 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

  1. Use indexes for frequently queried fields
  2. Limit results to avoid loading too much data
  3. Project only needed fields to reduce transfer size
  4. Use exists() instead of count() > 0 for existence checks
  5. Use estimatedDocumentCount() when you don't need exact counts

Released under the MIT License.