Reputation: 1610
So I'm learning mongoose
and I've implemented a Customer
model like this:
let CustomerSchema = new Schema({
stripe_id: {
type: String,
required: true
},
telegram_id: {
type: Number
},
email: {
type: String,
required: true
},
subscriptions: [SubscriptionSchema],
created_at: {
type: Date,
default: Date.now,
required: true
}
});
essentially I would like to return all the subscriptions of a customer, but how can I search in nested document, in this case subscriptions
?
This is the subscription model:
let SubscriptionSchema = new Schema({
status: {
type: String,
required: true
},
plan_id: {
type: String,
required: true
}
});
I would like to return only the subscriptions which have as status active
, at the moment I'm able to search for customer as:
let customer = await CustomerModel.findOne({telegram_id: ctx.chat.id});
Upvotes: 1
Views: 63
Reputation: 17888
You can use the filter aggregation to filter in a nested array.
Sample express route with mongoose:
router.get("/customers/:id", async (req, res) => {
let result = await Customer.aggregate([
{
$match: {
telegram_id: 1 //todo: req.params.id
}
},
{
$project: {
_id: "$_id",
stripe_id: "$stripe_id",
telegram_id: "$telegram_id",
email: "$email",
subscriptions: {
$filter: {
input: "$subscriptions",
as: "item",
cond: {
$eq: ["$$item.status", "active"]
}
}
}
}
}
]);
//todo: result will be an array, you can return the result[0] if you want to return as object
res.send(result);
});
Let'a say we have the following document:
{
"_id" : ObjectId("5e09eaa1c22a8850c01dff77"),
"stripe_id" : "stripe_id 1",
"telegram_id" : 1,
"email" : "[email protected]",
"subscriptions" : [
{
"_id" : ObjectId("5e09eaa1c22a8850c01dff7a"),
"status" : "active",
"plan_id" : "plan 1"
},
{
"_id" : ObjectId("5e09eaa1c22a8850c01dff79"),
"status" : "passive",
"plan_id" : "plan 2"
},
{
"_id" : ObjectId("5e09eaa1c22a8850c01dff78"),
"status" : "active",
"plan_id" : "plan 3"
}
],
"created_at" : ISODate("2019-12-30T15:16:33.967+03:00"),
"__v" : 0
}
The result will be like this:
[
{
"_id": "5e09eaa1c22a8850c01dff77",
"stripe_id": "stripe_id 1",
"telegram_id": 1,
"email": "[email protected]",
"subscriptions": [
{
"_id": "5e09eaa1c22a8850c01dff7a",
"status": "active",
"plan_id": "plan 1"
},
{
"_id": "5e09eaa1c22a8850c01dff78",
"status": "active",
"plan_id": "plan 3"
}
]
}
]
If you don't want to project the items one by one, you can use addFields aggregation like this:
router.get("/customers/:id", async (req, res) => {
let result = await Customer.aggregate([
{
$match: {
telegram_id: 1
}
},
{
$addFields: {
subscriptions: {
$filter: {
input: "$subscriptions",
as: "item",
cond: {
$eq: ["$$item.status", "active"]
}
}
}
}
}
]);
res.send(result);
});
Upvotes: 1
Reputation: 890
You can do something like this.
await CustomerModel.findOne({telegram_id: ctx.chat.id})
.populate({
path: 'subscriptions',
match: {status: 'active'}
});
here path
is used to join the next model and match
is used to query inside that model.
Upvotes: 1