skip to Main Content

Lookup:

const course = await mongoose.model('Course').aggregate()
    .match({ _id: new mongoose.Types.ObjectId(id) })
    .lookup({
        from: 'User',
        foreignField: '_id',
        localField: 'userId',
        as: 'user'
    })

Course schema:

const CourseSchema = new Schema({
  title: String,
  userId: {
    type: mongoose.Types.ObjectId,
    ref: 'User'
  },
}, { strict: false });

Expected output:

{
    "course": [
        {
            "_id": "64b000c085e9b1aec5975249",
            "title": "test",
            "userId": "64affc47d81a4bfba92b5540",
            "user": [], // data here should be present but empty why?
        }
    ]
}

How do I get user data in the above output?

Works using populate:

const course = await mongoose.model('Course')
            .find({ _id: new mongoose.Types.ObjectId(id) })
            .populate('userId').exec();

But how to achieve this using aggregation pipeline?

2

Answers


  1. If your userId in Course collection is objectId instead of plain String
    you can aggregate result by following query,

    db.Course.aggregate([{$match:{_id:ObjectId("yourId")}}
    ,{$lookup:{
      from: 'User',
      localField: 'userId',
      foreignField: "_id",
      as: 'users'
    }}])
    

    if you have userId as plain String you need to use $addFields to convert your string id to ObjectId.

    db.Course.aggregate([{$match:{_id:ObjectId("yourId")}}
    ,{$addFields:{'userId':{"$toObjectId":"$userId"}}}
    ,{$lookup:{
      from: 'User',
      localField: 'userId',
      foreignField: "_id",
      as: 'users'
    }}])
    
    Login or Signup to reply.
  2. The collection name is users, not User. See option: collection

    Mongoose by default produces a collection name by passing the model name to the utils.toCollectionName method. This method pluralizes the name.

    Let’s see the utils.toCollectionName method:

    exports.toCollectionName = function(name, pluralize) {
      if (name === 'system.profile') {
        return name;
      }
      if (name === 'system.indexes') {
        return name;
      }
      if (typeof pluralize === 'function') {
        return pluralize(name);
      }
      return name;
    };
    
    function pluralize(str) {
      let found;
      str = str.toLowerCase();
      if (!~uncountables.indexOf(str)) {
        found = rules.filter(function(rule) {
          return str.match(rule[0]);
        });
        if (found[0]) {
          return str.replace(found[0][0], found[0][1]);
        }
      }
      return str;
    }
    

    See full source code

    As you can see, the model name 'User' will be converted to the collection name "users". So it should be from: "users", not from: "User" in lookup() method.

    The localField and foreignField are wrong. The localField is the field in the local collection which is cources. The foreignField is the field in the foreign collection which is users.

    • from: <foreign collection>
    • localField: <field from local collection's documents>,
    • foreignField: <field from foreign collection's documents>

    A working example:

    import mongoose from 'mongoose';
    import util from 'util';
    import { config } from '../../config';
    
    mongoose.set('debug', true);
    console.log(mongoose.version);
    
    const UserSchema = new mongoose.Schema({ name: String });
    const User = mongoose.model('User', UserSchema);
    
    const CourseSchema = new mongoose.Schema(
        {
            title: String,
            userId: {
                type: mongoose.Types.ObjectId,
                ref: 'User',
            },
        },
        { strict: false },
    );
    const Course = mongoose.model('Course', CourseSchema);
    
    (async function main() {
        try {
            await mongoose.connect(config.MONGODB_URI);
            // seed
            const [u1] = await User.create([{ name: 'user-a' }]);
            const [c] = await Course.create([{ title: 'course a', userId: u1 }]);
    
            const r = await Course.aggregate()
                .match({ _id: c?._id })
                .lookup({
                    from: 'users',
                    localField: 'userId',
                    foreignField: '_id',
                    as: 'user',
                });
    
            console.log(util.inspect(r, false, null));
        } catch (error) {
            console.error(error);
        } finally {
            await mongoose.connection.close();
        }
    })();
    

    Logs:

    7.3.2
    Mongoose: users.insertOne({ name: 'user-a', _id: ObjectId("64b0e2768c75418f819995af"), __v: 0 }, {})
    Mongoose: courses.insertOne({ title: 'course a', userId: ObjectId("64b0e2768c75418f819995af"), _id: ObjectId("64b0e2778c75418f819995b1"), __v: 0}, {})
    Mongoose: courses.aggregate([ { '$match': { _id: new ObjectId("64b0e2778c75418f819995b1") } }, { '$lookup': { from: 'users', localField: 'userId', foreignField: '_id', as: 'user' } }], {})
    [
      {
        _id: new ObjectId("64b0e2778c75418f819995b1"),
        title: 'course a',
        userId: new ObjectId("64b0e2768c75418f819995af"),
        __v: 0,
        user: [
          {
            _id: new ObjectId("64b0e2768c75418f819995af"),
            name: 'user-a',
            __v: 0
          }
        ]
      }
    ]
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search