Skip to main content

Filtering with MongoDB Operators

Lesan's filters (and filter) options accept raw MongoDB filters. Everything you can put in a MongoDB find works here โ€” comparison operators, logical operators, regex, $text, and more. Plus, because relations are embedded snapshots, you can filter on related data with dot-notation without any join.

Comparison Operatorsโ€‹

await cities.find({
filters: {
population: { $gt: 1000000 }, // greater than
name: { $ne: "Unknown" }, // not equal
code: { $in: ["TEH", "KAR"] }, // in array
age: { $gte: 18, $lte: 65 }, // range
},
}).toArray();

Logical Operatorsโ€‹

await cities.find({
filters: {
$and: [
{ population: { $gt: 1000000 } },
{ isCapital: true },
],
$or: [
{ name: { $regex: "^T" } },
{ name: { $regex: "^K" } },
],
},
}).toArray();

Filtering on Relations (dot notation)โ€‹

Relations are embedded as snapshots inside the document, so you filter on them as if they were ordinary nested fields:

// All cities in a country by name:
await cities.find({
filters: { "country.name": "Iran" },
}).toArray();

// Deeper chains work too:
await cities.find({
filters: { "country.region.name": "Middle East" },
}).toArray();

No $lookup, no second query, no joins โ€” the data is already there.

Filtering by Relation IDโ€‹

await cities.find({
filters: { "country._id": new ObjectId(countryId) },
}).toArray();

This is the same pattern used in aggregation $match stages.

Using Filters Inside an Actโ€‹

Never trust the client to send raw MongoDB operators. Expose clean parameters in your validator and build the filter server-side:

import { ActFn, number, object, optional, string } from "@hemedani/lesan";

const searchCitiesValidator = () =>
object({
set: object({
minPopulation: optional(number()),
provinceName: optional(string()),
text: optional(string()),
}),
get: coreApp.schemas.selectStruct("city", 1),
});

const searchCities: ActFn = async (body) => {
const { minPopulation, provinceName, text } = body.details.set;

const filters: Record<string, unknown> = {};
if (minPopulation) filters.population = { $gte: minPopulation };
if (provinceName) filters["province.name"] = provinceName;
if (text) filters.name = { $regex: text, $options: "i" };

return await cities.find({ filters, projection: body.details.get }).toArray();
};

Filtering by Relation Content in Aggregationโ€‹

The same dot-notation works inside $match:

const pipeline = [
{ $match: { "country._id": new ObjectId(countryId) } },
{ $sort: { population: -1 } },
];

Indexes Matterโ€‹

Every filter becomes a MongoDB query. Add indexes for the fields you filter or sort on most:

const cities = coreApp.odm.newModel("city", cityPure, cityRelations, {
createIndex: {
indexSpec: { "country._id": 1, population: -1 },
options: {},
},
});

See Model Options for the full createIndex signature.