This is my product model.
const featureHeaderSchema = new Schema({
header: {
type: String,
required: true,
},
});
const featureSchema = new Schema({
title: {
type: String,
required: true,
},
parentHeader: { type: Schema.Types.ObjectId, ref: "product.featureHeaders" }, // not sure if this is correct
parentFeature: { type: Schema.Types.ObjectId, ref: "product.features" }, // not sure if this is correct
});
const productSchema = new Schema(
{
title: {
type: String,
required: true,
},
thumbnail: String,
description: String,
manufacture: { type: Schema.Types.ObjectId, ref: "user" },
featureHeaders: {
type: [featureHeaderSchema],
},
features: {
type: [featureSchema],
},
},
{ timestamps: true }
);
const productModel = mongoose.model("product", productSchema);
I wanted to find a product by its id and get parentHeader's title and parentFeature's title with populate.
I tried to get product info by product ID and populate on parentHeader and parentFeature so I could have their titles on my output.
I tried this query:
const output = await productModel
.findById(newProduct._id)
.populate("features.parentFeature")
.exec();
but I get this error: MissingSchemaError: Schema hasn't been registered for model "product.features".
Firstly, what your trying to do isn't possible with populate
. That's because the ref
property in the schema declaration is for referencing a model, not a path. It's used to tell mongoose which model you want to use to perform the $lookup
and mongoose expects that model to correspond to a collection in your database.
Secondly, you are using subdocuments in your productSchema
for the featureHeaders
and features
arrays so populate
won't work for you regardless.
Thankfully you can fix this and get populate
working for you. Although your naming convention and use case is a little confusing, hopefully this will help to explain how it's done:
FeatureHeader
and Feature
need to have their own collection, not be subdocuments so you need to create models for them.import mongoose from "mongoose";
const featureHeaderSchema = new mongoose.Schema({
header: {
type: String,
required: true,
}
});
// You need create the FeatureHeader model
const FeatureHeader = mongoose.model("FeatureHeader", featureHeaderSchema);
const featureSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
parentHeader: {
type: mongoose.Schema.Types.ObjectId,
ref: "FeatureHeader" //< Reference the FeatureHeader model
},
parentFeature: {
type: mongoose.Schema.Types.ObjectId,
ref: "Feature" //< You can reference the same model (i.e self)
}
});
// You need create the Feature model
const Feature = mongoose.model("Feature", featureSchema);
const productSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
thumbnail: String,
description: String,
manufacture: {
type: mongoose.Schema.Types.ObjectId,
ref: "User" //< Capitalise the model name
},
featureHeaders: [{
type: mongoose.Schema.Types.ObjectId,
ref: "FeatureHeader", //< Reference the FeatureHeader model
}],
features: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Feature", //< Reference the Feature model
}],
},
{ timestamps: true });
// You need create the Feature model
const Product = mongoose.model("Product", productSchema);
FeatureHeader
and Feature
need to be saved separately in their own collection in order to be referenced .const newFeatureOne = await Feature.create({
title: 'Marvel',
});
const newFeatureHeader = await FeatureHeader.create({
header: 'Superhero'
});
const newFeatureTwo = await Feature.create({
title: 'Repulsor Beams',
parentHeader: newFeatureHeader._id,
parentFeature: newFeatureOne._id
});
const newProduct = await Product.create({
title: 'Repulsor Beams',
//...
//...
featureHeaders: [newFeatureHeader._id],
features: [newFeatureTwo._id],
});
populate
to replace the ObjectId
with their corresponding document:const id = req.body.id; //< Could be req.params.id or however you get the id
const product = await Product.findById(id)
.populate("featureHeaders")
.populate("features");
Note: Depending on your design and how the models are scoped you may need to specify the path
and/or the model
properties of the populate
options object like so:
const id = req.body.id; //< Could be req.params.id or however you get the id
const product = await Product.findById(id)
.populate({path: "featureHeaders", model: "FeatureHeader"})
.populate({path: "features", model: "Feature"});
Hopefully all of that makes sense and if you have any questions then please ask in the comments.