Skip to content
EventFabric CQRS Framework

CRUD+

The MongoDB package provides low-level CRUD functions for direct database operations. These functions are fully instrumented with OpenTelemetry tracing and metrics, and handle errors using EventFabric exceptions.

An in Depth Example

This guide also has an in depth example of a working application built with EventFabric. Combining DDD, CQRS and Event Sourcing.

Check out the In Depth Example page to learn how everything is connected and works out in a real-world application.

Use these low-level functions when:

  • You need operations not provided by MongoDBRepository
  • You want direct control over MongoDB operations
  • You’re building custom repository methods

For standard CRUD operations, prefer using the Repository class.

FunctionDescription
findFind multiple documents matching a filter
findOneFind a single document matching a filter
insertOneInsert a single document
insertManyInsert multiple documents
replaceOneReplace a single document
updateOneUpdate a single document
updateManyUpdate multiple documents
deleteOneDelete a single document
deleteManyDelete multiple documents
countDocumentsCount documents matching a filter
bulkWriteExecute multiple write operations
aggregateExecute an aggregation pipeline
findOneAndUpdateFind and update a document atomically
findOneAndReplaceFind and replace a document atomically
findOneAndDeleteFind and delete a document atomically

Functions that return typed data require mapDocument and outputType parameters for type-safe results.

import { find, findOne } from "@nimbus-cqrs/mongodb";

type User = { _id: string; email: string; name: string };

// Find multiple documents
const users = await find<User>({
    collection,
    filter: { status: "active" },
    limit: 10,
    skip: 0,
    sort: { createdAt: -1 },
    mapDocument: (doc) => ({
        _id: doc._id.toString(),
        email: doc.email,
        name: doc.name,
    }),
    outputType: UserSchema,
});

// Find a single document
const user = await findOne<User>({
    collection,
    filter: { email: "john@example.com" },
    mapDocument: (doc) => ({
        _id: doc._id.toString(),
        email: doc.email,
        name: doc.name,
    }),
    outputType: UserSchema,
});
import { insertOne, insertMany } from "@nimbus-cqrs/mongodb";

// Insert a single document
const result = await insertOne({
    collection,
    document: {
        email: "john@example.com",
        name: "John Doe",
        createdAt: new Date(),
    },
});

// Insert multiple documents
const results = await insertMany({
    collection,
    documents: [
        { email: "john@example.com", name: "John" },
        { email: "jane@example.com", name: "Jane" },
    ],
});
import { updateOne, updateMany, replaceOne } from "@nimbus-cqrs/mongodb";

// Update a single document
const result = await updateOne({
    collection,
    filter: { _id: userId },
    update: { $set: { name: "New Name", updatedAt: new Date() } },
});

// Update multiple documents
const results = await updateMany({
    collection,
    filter: { status: "pending" },
    update: { $set: { status: "processed" } },
});

// Replace a document entirely
const replaced = await replaceOne({
    collection,
    filter: { _id: userId },
    replacement: {
        email: "new@example.com",
        name: "New Name",
        updatedAt: new Date(),
    },
});
import { deleteOne, deleteMany } from "@nimbus-cqrs/mongodb";

// Delete a single document
const result = await deleteOne({
    collection,
    filter: { _id: userId },
});

// Delete multiple documents
const results = await deleteMany({
    collection,
    filter: { status: "deleted" },
});

These functions return the document before or after modification, requiring mapDocument and outputType for type safety.

import {
    findOneAndUpdate,
    findOneAndReplace,
    findOneAndDelete,
} from "@nimbus-cqrs/mongodb";

type User = { _id: string; email: string; loginCount: number };

// Find and update atomically
const updated = await findOneAndUpdate<User>({
    collection,
    filter: { _id: userId },
    update: { $inc: { loginCount: 1 } },
    mapDocument: (doc) => ({
        _id: doc._id.toString(),
        email: doc.email,
        loginCount: doc.loginCount,
    }),
    outputType: UserSchema,
    options: { returnDocument: "after" },
});

// Find and replace atomically
const replaced = await findOneAndReplace<User>({
    collection,
    filter: { _id: userId },
    replacement: newDocument,
    mapDocument: (doc) => ({
        _id: doc._id.toString(),
        email: doc.email,
        loginCount: doc.loginCount,
    }),
    outputType: UserSchema,
    options: { returnDocument: "after" },
});

// Find and delete atomically
const deleted = await findOneAndDelete<User>({
    collection,
    filter: { _id: userId },
    mapDocument: (doc) => ({
        _id: doc._id.toString(),
        email: doc.email,
        loginCount: doc.loginCount,
    }),
    outputType: UserSchema,
});

The aggregate function executes a pipeline and maps results to typed output.

import { aggregate } from "@nimbus-cqrs/mongodb";

type CategoryCount = { category: string; count: number };

const results = await aggregate<CategoryCount>({
    collection,
    aggregation: [
        { $match: { status: "active" } },
        { $group: { _id: "$category", count: { $sum: 1 } } },
        { $sort: { count: -1 } },
    ],
    mapDocument: (doc) => ({
        category: doc._id,
        count: doc.count,
    }),
    outputType: CategoryCountSchema,
});
import { bulkWrite } from "@nimbus-cqrs/mongodb";

const result = await bulkWrite({
    collection,
    operations: [
        { insertOne: { document: { name: "New Item" } } },
        {
            updateOne: {
                filter: { _id: id1 },
                update: { $set: { status: "updated" } },
            },
        },
        { deleteOne: { filter: { _id: id2 } } },
    ],
});
import { countDocuments } from "@nimbus-cqrs/mongodb";

const count = await countDocuments({
    collection,
    filter: { status: "active" },
});

All CRUD functions are automatically instrumented with OpenTelemetry tracing and metrics.

Each operation creates a span with the following attributes:

AttributeDescription
db.systemAlways mongodb
db.operationThe operation name (e.g., find)
db.mongodb.collectionThe collection name

Two metrics are recorded for every operation:

MetricTypeLabelsDescription
mongodb_operation_totalCounteroperation, collection, statusTotal number of operations
mongodb_operation_duration_secondsHistogramoperation, collectionDuration of operations in seconds

The status label is either success or error.

All functions use handleMongoError internally to convert MongoDB errors to EventFabric exceptions. See handleMongoError for details on error mapping.