Q:

MongoDB: how to insert a sub-document?

I am using sub-documents in my MEAN project, to handle orders and items per order.
These are my (simplified) schemas:
var itemPerOrderSchema = new mongoose.Schema({
  itemId: String,
  count: Number
});

var OrderSchema = new mongoose.Schema({
  customerId: String,
  date: String,
  items: [ itemPerOrderSchema ]
});

To insert items in itemPerOrderSchema array I currently do:
var orderId = '123';
var item = { itemId: 'xyz', itemsCount: 7 };
Order.findOne({ id: orderId }, function(err, order) {
  order.items.push(item);
  order.save();
});

The problem is that I obviously want one item per itemId, and this way I obtain many sub-documents per item...
One solution could be to loop through all order.items, but this is not optimal, of course (order.items could me many...).
The same problem could arise when querying order.items...
The question is: how do I insert items in itemPerOrderSchema array without having to loop through all items already inserted on the order?

A:

If you can use an object instead of array for items, maybe you can change your schema a bit for a single-query update.
Something like this:
{
  customerId: 123,
  items: {
     xyz: 14,
     ds2: 7
  }
}

So, each itemId is a key in an object, not an element of the array.
let OrderSchema = new mongoose.Schema({
  customerId: String,
  date: String,
  items: mongoose.Schema.Types.Mixed
});

Then updating your order is super simple. Let's say you want to add 3 of items number 'xyz' to customer 123.
db.orders.update({
  customerId: 123
},
{
  $inc: {
    'items.xyz': 3
  }
}, 
{
  upsert: true
});

Passing upsert here to create the order even if the customer doesn't have an entry.
The downsides of this:

it is that if you use aggregation framework, it is either impossible to iterate over your items, or if you have a limited, known set of itemIds, then very verbose. You could solve that one with mapReduce, which can be a little slower, depending on how many of them you have there, so YMMB.
you do not have a clean items array on the client. You could fix that with either client extracting this info (a simple let items = Object.keys(order.items).map(key => ({ key: order.items[key] })); or with a mongoose virtual field or schema.path(), but this is probably another question, already answered.

