Querying
All read methods on MongoCollectionService and MongoNestedService accept a shared set of options for filtering, shaping, and paginating results.
Filtering
Pass a filter object in the options. The syntax mirrors MongoDB query operators and is translated by MongoAdapter.prepareFilter before hitting the driver.
// Simple equality
const customers = await svc.findMany({ filter: { status: 'active' } });
// Multiple conditions (implicit $and)
const customers = await svc.findMany({
filter: { status: 'active', country: 'US' },
});
// MongoDB operators
const customers = await svc.findMany({
filter: { createdAt: { $gte: new Date('2024-01-01') } },
});
For complex expressions you can pass any valid MongoDB filter object — the filter option is typed as FilterInput<T>, which accepts anything the MongoDB driver accepts.
documentFilter — global row-level constraint
documentFilter is applied to every read and write operation on a service instance. Use it to implement multi-tenancy, soft deletes, or any constraint that must never be bypassed:
export class CustomersService extends MongoCollectionService<Customer> {
constructor(readonly tenantId: string) {
super(Customer, {
documentFilter: { tenantId },
});
}
}
It can also be a function computed per request:
documentFilter: (command, _this) => ({
tenantId: _this.context.tenantId,
deletedAt: { $exists: false },
})
Use documentFilter for constraints that must always hold. Use the method-level filter option for ad-hoc query criteria.
Projection
Control which fields are returned. Pass an array of field names or '*' for all fields.
// Return only id and name
const customers = await svc.findMany({
filter: { status: 'active' },
projection: ['_id', 'name', 'email'],
});
// TypeScript return type narrows to PartialDTO<Customer> when projection is supplied
const partial = await svc.get(id, { projection: ['_id', 'name'] });
partial.name; // ✓
partial.email; // ✗ — not in projection, TypeScript error
Sorting
Pass an array of field names. Prefix with - for descending order.
const customers = await svc.findMany({
sort: ['status', '-createdAt'], // status ASC, createdAt DESC
});
Pagination
Use limit and skip together, or call findManyWithCount to get the total in one round trip.
const PAGE_SIZE = 20;
const page = 3;
const { items, count } = await svc.findManyWithCount({
filter: { status: 'active' },
sort: ['-createdAt'],
limit: PAGE_SIZE,
skip: (page - 1) * PAGE_SIZE,
});
console.log(`Page ${page} of ${Math.ceil(count / PAGE_SIZE)}`);
findMany applies defaultLimit (default 10) when limit is not specified. Set it in the constructor to change the service default:
super(Customer, { defaultLimit: 50 });
Aggregation stages
preStages and postStages let you inject raw MongoDB aggregation pipeline stages before or after the standard filter/sort/limit stages. Use them for lookups, computed fields, or any operation the standard options don't cover.
// Add a $lookup before filtering
const results = await svc.findMany({
filter: { status: 'active' },
preStages: [
{
$lookup: {
from: 'orders',
localField: '_id',
foreignField: 'customerId',
as: 'orders',
},
},
],
});
// Add a computed field after pagination
const results = await svc.findMany({
postStages: [
{ $addFields: { fullName: { $concat: ['$firstName', ' ', '$lastName'] } } },
],
});
Pipeline order with both options set:
preStages → $match (filter) → $skip → $sort → $limit → postStages → $project (projection)
Checking existence
// Does this specific document exist?
const exists = await svc.exists(id);
// Does any document match a filter?
const hasActive = await svc.existsOne({ filter: { status: 'active' } });
// Assert existence — throws ResourceNotAvailableError if not found
await svc.assert(id);