typescriptmongodbexpressmongoosemongoose-populate

Mongoose returning empty array after populate() method


Banged my head against the wall for past 3-4 hours and checked countless articles here on StackOverflow but could not get my response to populate an array correctly. Using Express.js with Typescript, MongoDB and mongoose. Issue is when I get a response with all the orders my orderedlines array is empty even though I can check and see the ids are there in MongoDB atlas. Here is the actual response:

[
  {
    "orderedlines": [],
    "_id": "6251c61f7385c349f88fe95a",
    "userId": {
      "favourites": [
        "623b39e684b9baf1109053f8",
        "623b3afada0e7828602c78df",
        "623b3b49da0e7828602c78e7",
        "623b39ba84b9baf1109053f7",
        "623b3b59da0e7828602c78e9"
      ],
      "_id": "62326179b9c85d3fc833d686",
      "orders": [],
      "email": "testche_emailche@gmail.com",
      "username": "stef1222",
      "password": "$2b$10$3e5Y/IoyrcJHH3ud6Mn/I.8PfBm2JrEKHwYRd8cQwUaAdz.YkKSMa",
      "firstName": "Stefan",
      "lastName": "Georgiev",
      "image": "https://res.cloudinary.com/dtggdx3hc/image/upload/v1648046254/deqr4chfysogoppafdug.png",
      "isAdmin": false,
      "hasWriteAccess": false,
      "__v": 0
    },
    "totalPrice": 121.99,
    "__v": 0
  }
]

As seen above my userId is being populated successfully with all its properties but orderedlines is failing to populate and it is returned as empty array. If I remove the .populate() it returns an array of objects with ids

My findOrdersForUserId function in orderServices where I assume the problem occurs

const findOrdersForUserId = async (
  userId: string
): Promise<OrderDocument[]> => {
  const ordersToReturn = await Order.find({ userId: userId })
    .sort({ _id: 1 })
    .populate('userId')
    .populate({path:'orderedlines', model:OrderLine})
  return ordersToReturn
}

Here is my Order model:

import mongoose, { Document } from 'mongoose'

export type OrderLine = {
  orderlineId: string
}

export type OrderDocument = Document & {
  userId: string
  orderedlines: OrderLine[]
  totalPrice: number
}

const orderSchema = new mongoose.Schema({
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  totalPrice: Number,
  orderedlines: [
    {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'OrderLine',
    },
  ],
})

export default mongoose.model<OrderDocument>('Order', orderSchema, 'orders')

My OrderLine model:

import mongoose, { Document } from 'mongoose'

export type OrderLineDocument = Document & {
  productId: string
  userId: string
  quantity: number
  price: number
}

const orderLineSchema = new mongoose.Schema({
  quantity: { type: Number, default: 1 },
  price: Number,
  productId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Product',
    required: true,
  },
  userId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true,
  },
})

export default mongoose.model<OrderLineDocument>('OrderLine', orderLineSchema, 'orderlines')

Mongoose version used: mongoose@5.13.10
Node version: v16.13.0
Mongo Shell version: v5.0.6
Express version: express@4.17.1

I would list some of the articles I tried to fix my the issue without success:

  1. Mongoose populate returning empty array
  2. Mongoose populate() returning empty array
  3. Mongoose populate() returns empty array with no errors
  4. Mongoose Populate not working with Array of ObjectIds
  5. Populate method not populating my comment array
  6. Mongoose populate does not populate array
  7. Mongoose populate not populating an array and always returns an empty array

Edit: All the code is a part of a fork from a repo that is set to private. I am not sure how can I share access/link to the fork for a better preview of the code. Please enlighten me if you would like to see something else or a more specific part of the code.


Solution

  • Answering my own question because I struggled with this a lot and want to help future people reading this question. First of all and really important issue might be if you are not specificying a collection name. In order to specify a collection name add a third argument in your model creation like so:

    export default mongoose.model<OrderLineDocument>('OrderLine',orderLineSchema,'orderLines')
    

    Before the third argument mongoose was creating a collection name with a lower-cased plurarized model name - orderlines. But when I was trying to populate my response I was referring to orderLines with a capital L. So heads up always specify your collection name or check your collection name in your MongoDB Shell/ MongoDB Atlas.

    Second of all. Always check if the _ids your array/object that you want to populate are correct _ids that exist in your database. If they are non-existent or wrong the response would either contain empty array or even would return error.

    For example I was adding this in the beginning when the error was occuring but those _ids were not existing (I had deleted them before but forgot) in my orderLines collection in the first place (you can still add _ids that are not existing if they are the correct _id structure so be aware)

    POST http://localhost:5000/api/v1/orders/62326179b9c85d3fc833d686
    Content-Type: application/json
    
    {
      "orderedlines":[
          {"_id": "6251be4d4186c34788e4f034"},
          {"_id": "6251be494186c34788e4f030"}
      ],
      "totalPrice":"121.99"
    }
    

    Third of all. If you are using TypeScript there are three approaches that work:

    1. Using your own type
    export type OrderLine = {
      _id: string
    }
    
    export type OrderDocument = Document & {
      userId: string
      orderedlines: OrderLine[],
      totalPrice: number
    }
    
    1. Saying this would be an array of string
    export type OrderDocument = Document & {
      userId: string
      orderedlines: string[],
      totalPrice: number
    }
    
    1. Array of ObjectIds
    export type OrderDocument = Document & {
      userId: string
      orderedlines: mongoose.Schema.Types.ObjectId[]
      totalPrice: number
    }
    

    After all of the above three things are taken in consideration what worked for me in the end was this approach:

    const findOrdersForUserId = async (
      userId: string
    ): Promise<OrderDocument[]> => {
      const ordersToReturn = await Order.find({ userId: userId })
        .sort({ _id: 1 })
        .populate('userId')
        .populate({
          path: 'orderedlines',
          populate: { path: 'productId', model: 'Product' },
        })
      return ordersToReturn
    }
    

    And this is a part of the response I am getting just to showcase that both orderedline, productId and userId are being populated correctly

    {
            "quantity": 4, //part of a single orderedline object
            "_id": "6251be494186c34788e4f030", //part of a single orderedline object
            "productId": { // beginning of populated productId
              "_id": "623b3b24da0e7828602c78e3",
              "name": "AMY SS14R - TRICKSTER",
              "image": "https://res.cloudinary.com/dtggdx3hc/image/upload/v1648048425/amy-deluxe-stick-steel-r-ss09r10_1000x1000_bfngtl.jpg",
              "price": 1299,
              "__v": 0
            }, // end of populated productId
            "price": 2.99, //part of a single orderedline object
            "__v": 0 //part of a single orderedline object
          }
        ], // end of the orderedline array of objects
        "_id": "6252bfa8dd00081d7c273e4d", // _id of the order
        "userId": { //beginning of populated userId
          "favourites": [
            "623b39e684b9baf1109053f8",
            "623b3afada0e7828602c78df",
            "623b3b49da0e7828602c78e7",
            "623b39ba84b9baf1109053f7",
            "623b3b59da0e7828602c78e9"
          ],