node.jsmongodbmongooseaggregation-framework

is it possible to check whether the user-given foreignFieldId (req.body.foreignFieldId ) exists or not before calling db.create(req.body) in MongoDB


Product Schema:

const productSchema = new mongoose.Schema({
   name: { type: String, required: true },
   price: { type: Number, required: true },
   addedBy: { type: mongoose.Types.ObjectId, ref: 'User' },
   categoryId: { type: mongoose.Types.ObjectId, ref: 'Category' }, //holds the id **AFTER** creation
   isDeleted: { type: Boolean, default: false },
},
{ 
   timestamps: true,
   versionKey: false 
});

Category Schema:

const statusEnum = ["active", "inactive"]

const categorySchema = new mongoose.Schema({
   name: { type: String, required: true },
   status: { type: String, enum: statusEnum, default: "active" },
   isDeleted: { type: Boolean, default: false },
}, 
{
   timestamps: true,
   versionKey: false
});

I want to check, before creating a new entry in my MongoDB database, if a category in categories collection exists with the user given categoryId.

Is it possible using an aggregation, because I don't want to import the CategoryModel into my ProductControlller?


Solution

  • It is possible to do this using an aggregation but it is really a hack/workaround when there is a clear solution - use the CategoryModel to query first.

    Nonetheless, in case you are simply testing the efficiency of different approaches, with Model.aggregate() the logical steps might be:

    1. Query the database to retrieve any Product. It doesn't matter which document is returned we just need a single document to act as the host.
    2. $set a new field on that document named test_id using the user supplied categoryId from the request.
    3. Perform a $lookup on the categories collection matching on the new test_id field.
    4. Use an $unwind stage to unwind the array of objects that were assigned to test_id. If there was a match, $unwind will leave a single document - confirming that the categories collection has a document with that _id.
    5. If there wasn't a match the test_id field will be an empty array and given that:

    $unwind does not output a document if the field value is null, missing, or an empty array.

    You would therefore be left with zero documents - confirming that the categories collection doesn't have a document with that _id.

    It might look like this:

    const docs = await ProductModel.aggregate([
      {
        $sort: { _id: 1 }
      },
      {
        $limit: 1
      },
      {
        $set: {
          test_id: mongoose.Types.ObjectId.createFromHexString(req.body.categoryId)
        }
      },
      {
        $lookup: {
          from: "categories",
          localField: "test_id",
          foreignField: "_id",
          as: "test_id"
        }
      },
      {
        $unwind: "$test_id"
      }
    ])
    
    if(docs.length){
       // A document does exists with that categoryId 
       // so free now to do a ProductModel.create()
    }else{
       // docs is an empty array so there was no document with categoryId
       // need to return an error or whatever
    }
    

    Note: the $unwind stage isn't quite necessary. You could simply check for if(docs.test_id.length) but I feel leaving an empty docs array is somewhat more conclusive semantically and this solution isn't something that I would recommend over just checking using the CategoryModel.