Search code examples
node.jsmongodbexpressmongoose-schema

mongoose move ref document to main document as DTO


I wanna to move sub document to main documents, and return single DTO without any nested document, below is my sample data.js

data.js

const mongoose = require('mongoose');

//city
const citySchema = new mongoose.Schema({
    cityName: { type: String, required: true, unique: true },
});

const City = mongoose.model('City', citySchema);

//country
const countrySchema = new mongoose.Schema({
    countryName: { type: String, required: true, unique: true },
});

const Country = mongoose.model('Country', countrySchema);

//user
const userSchema = new mongoose.Schema({
    username: { type: String, required: true },
    city: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'City',
        required: true,
    },
    country: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Country',
        required: true,
    },
    createdAt: { type: Date, required: true },
    updatedAt: { type: Date, required: true },
});

const User = mongoose.model('User', userSchema);


function getUser(id) {
    return User.findById(id)
        .populate('city')
        .populate('country')
        .exec();
};

Current return JSON Response for User:

{
  "_id": "6321ac3d14a57c2716f7f4a0",
  "name": "David",
  "city": {
      "_id": "63218ce557336b03540c9ce9",
      "cityName": "New York",
      "__v": 0
  },
  "country": {
      "_id": "632185bbe499d5505cafdcbc",
      "countryName": "USA",
      "__v": 0
  },
  "createdAt": "2022-09-14T10:26:05.000Z",
  "__v": 0
}

How do I move the cityName and countryName to main model, and response JSON as below format?

{
  "_id": "6321ac3d14a57c2716f7f4a0",
  "username": "David",
  "cityName": "New York",
  "countryName": "USA",
  "createdAt": "2022-09-14T10:26:05.000Z",
}

Solution

  • Using aggregation you can try something like this:

    db.user.aggregate([
      {
        "$match": {
          _id: "6321ac3d14a57c2716f7f4a0"
        }
      },
      {
        "$lookup": {
          "from": "city",
          "localField": "city",
          "foreignField": "_id",
          "as": "city"
        }
      },
      {
        "$lookup": {
          "from": "country",
          "localField": "country",
          "foreignField": "_id",
          "as": "country"
        }
      },
      {
        "$addFields": {
          "country": {
            "$arrayElemAt": [
              "$country",
              0
            ]
          },
          "city": {
            "$arrayElemAt": [
              "$city",
              0
            ]
          }
        }
      },
      {
        "$addFields": {
          "countryName": "$country.countryName",
          "cityName": "$city.cityName"
        }
      },
      {
        "$unset": [
          "country",
          "city"
        ]
      }
    ])
    

    Here's the playground link. The other probably simpler way of doing this would be, modify your function like this:

    function getUser(id) {
        const user = User.findById(id)
            .populate('city')
            .populate('country')
            .exec();
        if(user.country) {
          user.countryName = user.country.countryName;
        }
        if(user.city) {
          user.cityName = user.city.cityName;
        }
        delete user.country;
        delete user.city;
        return user
    };