Find and findOne
When you want to penetrate one step into the depth of relationships, find and findOne are the best choices. In a traditional Mongo setup you'd need an aggregation with a $lookup for a left join โ but since Lesan embeds all relations, a plain find/findOne already returns one level of relations: the document and its directly-related children.
findOne โ find a single documentโ
Add a getUser act:
const getUserValidator = () => {
return object({
set: object({
userId: objectIdValidation,
}),
get: coreApp.schemas.selectStruct("user", 1),
});
};
const getUser: ActFn = async (body) => {
const {
set: { userId },
get,
} = body.details;
return await users.findOne({
filters: { _id: new ObjectId(userId) },
projection: get,
});
};
coreApp.acts.setAct({
schema: "user",
actName: "getUser",
validator: getUserValidator(),
fn: getUser,
});
findOne accepts three inputs:
filtersโ the MongoDB findOne query.projectionโ the MongoDB projection operation.options(optional) โ the MongoDB findOptions.
Per-relation depth in selectStructโ
Finding a city or country is identical โ the only difference is the second argument of selectStruct. Instead of a single number, pass an object whose keys are relation names and whose values specify how deep this act may penetrate that specific relation:
const getCountryValidator = () => {
return object({
set: object({
countryId: objectIdValidation,
}),
get: coreApp.schemas.selectStruct("country", {
citiesByPopulation: 1,
users: 1,
capital: 1,
}),
});
};
const getCountry: ActFn = async (body) => {
const {
set: { countryId },
get,
} = body.details;
return await countries.findOne({
filters: { _id: new ObjectId(countryId) },
projection: get,
});
};
const getCityValidator = () => {
return object({
set: object({
cityId: objectIdValidation,
}),
get: coreApp.schemas.selectStruct("city", { country: 1, lovedByUser: 1 }),
});
};
const getCity: ActFn = async (body) => {
const {
set: { cityId },
get,
} = body.details;
return await cities.findOne({
filters: { _id: new ObjectId(cityId) },
projection: get,
});
};
The full example is examples/document/06-1-find-one.ts.
Executing main โ user โ getUser:
Executing main โ city โ getCity:
Executing main โ country โ getCountry:
find โ find many documentsโ
Add a getUsers act for paginated lists:
const getUsersValidator = () => {
return object({
set: object({
page: number(),
limit: number(),
}),
get: coreApp.schemas.selectStruct("user", 1),
});
};
const getUsers: ActFn = async (body) => {
let {
set: { page, limit },
get,
} = body.details;
page = page || 1;
limit = limit || 50;
const skip = limit * (page - 1);
return await users
.find({ projection: get, filters: {} })
.skip(skip)
.limit(limit)
.toArray();
};
coreApp.acts.setAct({
schema: "user",
actName: "getUsers",
validator: getUsersValidator(),
fn: getUsers,
});
find accepts the same three inputs as findOne, and returns a MongoDB cursor โ so you can chain .skip(), .limit(), .sort(), and end with .toArray().
Just like with findOne, per-relation depth is set through the selectStruct second argument:
get: coreApp.schemas.selectStruct("country", {
citiesByPopulation: 1,
users: 1,
capital: 1,
}),
The full example is examples/document/06-2-find-methods.ts.
Executing main โ user โ getUsers:
Executing main โ city โ getCities:
Executing main โ country โ getCountries:
When do you need aggregation?โ
find/findOne give you one embedded level. If the client asks for country โ cities โ users (two steps), the projection can no longer be satisfied from the embedded snapshot alone โ Lesan has to $lookup into other collections. That's when you switch to aggregation, which is one step behind the request and resolves the rest automatically.
Next stepsโ
- Aggregation โ penetrating more than one step into relations
- Queries & Projections โ the full API reference for projections