Search code examples
arraysmongodbobjectmongoosemongoose-schema

How to add a virtual field to mongoose array of objects in a schema?


I have a mongoose schema as follow

const mongoose = require("mongoose");
const PostSchema = new mongoose.Schema(
  {
    title:{
        type:String,
        required: true,
        trim: true
    },
    author:{
      type: mongoose.Schema.Types.ObjectId,
      ref:'Customer'
    },
    images: [
        {img :{type:String}}
    ]
  },
  { timestamps: true }
);

PostSchema.virtual("images.imgCrop").get(function () {
   return `${this.img}/crop/720/720`;
 });
module.exports = mongoose.model("Post", PostSchema);

Data result is

[
    {
        "_id": "64cb808d95a1ec6708a8e5e6",
        "title": "First Post",
        "images": [],
        "createdAt": "2023-08-03T10:33:31.253Z",
        "updatedAt": "2023-08-03T10:33:31.253Z",
        "__v": 0
    },
    {
        "_id": "64cb829a95a1ec6708a8f9ab",
        "title": "Next Post",
        "images": [
            {
                "_id": "64cb829a95a1ec6708a8f9ac",
                "img": "image001.png"
            },
            {
                "_id": "64cb829a95a1ec6708a8f9ad",
                "img": "image002.png"
            },
            {
                "_id": "64cb829a95a1ec6708a8f9ae",
                "img": "image003.png"
            }
        ],
        "createdAt": "2023-08-03T10:34:02.173Z",
        "updatedAt": "2023-08-03T10:34:02.173Z",
        "__v": 0
    }
]

In the database I already have a bunch of data, So I need to modify new image path in the return query as imgCrop : "image003.png/crop/720/720"

Is it possible to achive this using mongoose virtual ?

Data is fetch as below

let posts = await Post.find({}).lean().populate('author')

So that the result should become as following

[
    {
        "_id": "64cb808d95a1ec6708a8e5e6",
        "title": "First Post",
        "images": [],
        "createdAt": "2023-08-03T10:33:31.253Z",
        "updatedAt": "2023-08-03T10:33:31.253Z",
        "__v": 0
    },
    {
        "_id": "64cb829a95a1ec6708a8f9ab",
        "title": "Next Post",
        "images": [
            {
                "_id": "64cb829a95a1ec6708a8f9ac",
                "img": "image001.png",
                "imgCrop": "image001.png/crop/720/720"
            },
            {
                "_id": "64cb829a95a1ec6708a8f9ad",
                "img": "image002.png",
                "imgCrop": "image002.png/crop/720/720"
            },
            {
                "_id": "64cb829a95a1ec6708a8f9ae",
                "img": "image003.png",
                "imgCrop": "image001.png/crop/720/720"
            }
        ],
        "createdAt": "2023-08-03T10:34:02.173Z",
        "updatedAt": "2023-08-03T10:34:02.173Z",
        "__v": 0
    }
]

Solution

  • You need to extract ImageSchema and declare a virtual on it.

    const ImageSchema = new mongoose.Schema({
      img: { type:String }
    });
    ImageSchema.virtual('imgCrop').get(function() {
      return `${this.img}/crop/720/720`;
    });
    
    const PostSchema = new mongoose.Schema(
      {
        title:{
            type: String,
            required: true,
            trim: true
        }
        images: [ImageSchema]
      },
      { timestamps: true }
    );
    

    Mind that the virtuals are not called when using lean() query.
    So you either need to drop lean() or install and use mongoose-lean-virtuals plugin:

    const mongooseLeanVirtuals = require('mongoose-lean-virtuals');
    
    PostSchema.plugin(mongooseLeanVirtuals);
    // Maybe also add this? ImageSchema.plugin(mongooseLeanVirtuals);
    
    Post.find({}).lean({ virtuals: true }).populate('author')