Skip to content

CRUD Operations

Monch provides a clean API for all database operations with automatic validation and type safety.

Create

insertOne

Insert a single document with full Zod validation:

typescript
const user = await Users.insertOne({
  name: 'Alice',
  email: 'alice@example.com',
});
// Returns the inserted document with _id and timestamps

insertMany

Insert multiple documents:

typescript
const users = await Users.insertMany([
  { name: 'Alice', email: 'alice@example.com' },
  { name: 'Bob', email: 'bob@example.com' },
]);
// Returns array of inserted documents

Each document is validated individually. If any document fails validation, the entire operation is rejected.

Read

find

Find multiple documents:

typescript
// Basic query
const users = await Users.find({ role: 'admin' }).toArray();

// With options
const recentUsers = await Users.find({ createdAt: { $gte: lastWeek } })
  .sort({ createdAt: -1 })
  .limit(10)
  .toArray();

// Empty filter returns all documents
const allUsers = await Users.find().toArray();

findOne

Find a single document:

typescript
const user = await Users.findOne({ email: 'alice@example.com' });
// Returns document or null

findById

Find by ObjectId (accepts string or ObjectId):

typescript
const user = await Users.findById('507f1f77bcf86cd799439011');
// or
const user = await Users.findById(new ObjectId('507f1f77bcf86cd799439011'));

count

Count matching documents:

typescript
const count = await Users.count({ role: 'admin' });

estimatedDocumentCount

Fast count using collection metadata (doesn't filter):

typescript
const total = await Users.estimatedDocumentCount();

exists

Check if any documents match:

typescript
const hasAdmins = await Users.exists({ role: 'admin' });
// Returns boolean

distinct

Get distinct values for a field:

typescript
const roles = await Users.distinct('role');
// ['user', 'admin', 'moderator']

// With filter
const activeRoles = await Users.distinct('role', { status: 'active' });

Update

updateOne

Update a single document:

typescript
const user = await Users.updateOne(
  { email: 'alice@example.com' },
  { $set: { role: 'admin' } }
);
// Returns updated document or null

Partial Validation: Only fields in $set and $setOnInsert are validated against the schema.

updateMany

Update multiple documents:

typescript
const count = await Users.updateMany(
  { role: 'user' },
  { $set: { verified: true } }
);
// Returns number of modified documents

Upsert

Insert if not found, update if exists:

typescript
const user = await Users.updateOne(
  { email: 'alice@example.com' },
  {
    $set: { lastLogin: new Date() },
    $setOnInsert: { name: 'Alice', role: 'user' },
  },
  { upsert: true }
);

Upsert Validation

For updateOne / findOneAndUpdate with upsert: true, Monch uses partial validation - only fields in $set and $setOnInsert are validated, not the entire document. This allows incremental document creation.

For replaceOne / findOneAndReplace with upsert: true, Monch uses full validation - the entire replacement document must satisfy the schema.

See Validation for details.

Replace

Replace an entire document:

typescript
const result = await Users.replaceOne(
  { _id: user._id },
  { name: 'Alice Smith', email: 'alice@example.com', role: 'admin' }
);
// Returns UpdateResult

The replacement document is fully validated against the schema.

Delete

deleteOne

Delete a single document:

typescript
const deleted = await Users.deleteOne({ email: 'alice@example.com' });
// Returns boolean (true if deleted)

deleteMany

Delete multiple documents:

typescript
const count = await Users.deleteMany({ status: 'inactive' });
// Returns number of deleted documents

Atomic Operations

These operations are atomic—they find and modify in a single operation.

findOneAndUpdate

typescript
const user = await Users.findOneAndUpdate(
  { email: 'alice@example.com' },
  { $inc: { loginCount: 1 } },
  { returnDocument: 'after' }  // Return updated document
);

findOneAndDelete

typescript
const deletedUser = await Users.findOneAndDelete(
  { email: 'alice@example.com' }
);
// Returns the deleted document

findOneAndReplace

typescript
const user = await Users.findOneAndReplace(
  { _id: userId },
  { name: 'Alice', email: 'alice@example.com', role: 'admin' },
  { returnDocument: 'after' }
);

No Hooks

Atomic operations (findOneAndUpdate, findOneAndDelete, findOneAndReplace) do not trigger lifecycle hooks.

Bulk Operations

Execute multiple operations in a single request:

typescript
const result = await Users.bulkWrite([
  { insertOne: { document: { name: 'Bob', email: 'bob@example.com' } } },
  { updateOne: { filter: { _id: id1 }, update: { $set: { name: 'Robert' } } } },
  { deleteOne: { filter: { _id: id2 } } },
  { replaceOne: { filter: { _id: id3 }, replacement: { name: 'Carol', email: 'carol@example.com' } } },
]);

Each operation is validated according to its type:

  • insertOne: Full validation
  • updateOne/updateMany: Partial validation
  • replaceOne: Full validation

No Hooks

bulkWrite uses Zod-only validation and does not trigger any lifecycle hooks.

Aggregation

Run aggregation pipelines:

typescript
const results = await Users.aggregate([
  { $match: { status: 'active' } },
  { $group: { _id: '$role', count: { $sum: 1 } } },
  { $sort: { count: -1 } },
]);

// Returns AggregationCursor - use .toArray() to get results
const data = await results.toArray();

Session Support

All operations accept an options object with session for transactions:

typescript
await Users.insertOne(
  { name: 'Alice', email: 'alice@example.com' },
  { session }
);

await Users.updateOne(
  { email: 'alice@example.com' },
  { $set: { role: 'admin' } },
  { session }
);

See Transactions for more details.

Released under the MIT License.