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": "[email protected]",
"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: [email protected]
Node version: v16.13.0
Mongo Shell version: v5.0.6
Express version: [email protected]
I would list some of the articles I tried to fix my the issue without success:
- Mongoose populate returning empty array
- Mongoose populate() returning empty array
- Mongoose populate() returns empty array with no errors
- Mongoose Populate not working with Array of ObjectIds
- Populate method not populating my comment array
- Mongoose populate does not populate array
- 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.
CodePudding user response:
You defined OrderLine
like this:
export type OrderLine = {
orderlineId: string
}
Then, you specified orderedlines
as an array of OrderLine
:
orderedlines: OrderLine[]
In your use case, orderedlines
are an array of ObjectId
and not an array of objects with nested property orderlineId
. TypeScript probably do some sort of casting when defining a model.
Try to change orderedlines
to be an array of ObjectId
and try again?
CodePudding user response:
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:
- Using your own type
export type OrderLine = {
_id: string
}
export type OrderDocument = Document & {
userId: string
orderedlines: OrderLine[],
totalPrice: number
}
- Saying this would be an array of string
export type OrderDocument = Document & {
userId: string
orderedlines: string[],
totalPrice: number
}
- 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"
],