Reputation: 3305
Say I have one model, Book
, and another model, Genre
. When I create the book, I'd like to be able to pass a Genre
ID and have the model automatically fetch and embed the document. For example:
const bookSchema = new Schema({
title: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
genre: {
type: ObjectId,
required: true,
}
});
const genreSchema = new Schema({
name: {
type: String,
required: true,
},
});
Then I'd like to be create a book as follows:
const Book = await Book.create({
title: 'Lord of the Rings',
author: 'J. R. R. Tolkien',
genre: '5d6ede6a0ba62570afcedd3a',
});
That would create a book and automatically embed the genre document from the given ID. Is there a way to do that from within the schema, or would I have to wrap it in additional logic?
Upvotes: 3
Views: 776
Reputation: 4692
How do you know which genre ID to embed? Can you send this from your frontend?
If yes, then simply select the genre ID from you frontend and then pass it in your API's request body.
While in your backend:
router.route('/book')
.post((req, res) => {
Book.create({
title: req.body.title,
author: req.body.author,
genre: req.body.genre,
}, (err, product) => {
if (err) {
res.send(err);
} else {
res.json({success:true})
}
});
})
Do something like this to create a new book object in your Book collection.
Upvotes: 0
Reputation: 718
you can use mixed schema type and document middleware to solve your problem.see my sample code below:
const genreSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
});
const Genre = mongoose.model('Genre', genreSchema);
const bookSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
genre: {
type: Object,
required: true,
}
});
bookSchema.pre('save', async function () {
const genreID = mongoose.Types.ObjectId(this.genre);
this.genre = await Genre.findById(genreID);
});
const Book = mongoose.model('Book', bookSchema);
const newBook = new Book({ title: 'The book', author: 'xyz', genre: '5ef55c67be27fb2a08a1131c' });
newBook.save();
Upvotes: 1
Reputation: 5245
You can use the pre-save mongoose middleware/hook to find the genre and set it as an embedded document. In mongoose pre-save hook, this
will be the current document, you can read the value and set the value to this
object before it is written to the database.
Note that, since this is a pre-save hook, it will be run only on Model.create()
or document.save()
. So it won't be run on Model.insertMany()
. But it will be run when you update the document using document.save()
. If you want to set the genre only on new documents, you will have to check for this.isNew
property
const { Schema, Types } = mongoose
const genreSchema = new Schema({
name: {
type: String,
required: true,
},
});
const Genre = mongoose.model('Genre', genreSchema)
const bookSchema = new Schema({
title: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
genreId: {
type: Schema.Types.ObjectId,
required: true,
},
genre: {
type: genreSchema,
},
});
bookSchema.pre('save', async function() {
if (this.isNew) { // this.isNew will be true on only new documents
this.genre = await Genre.findById(this.genreId) // `this` is the new book document
}
})
const Book = mongoose.model('Book', bookSchema)
/* Test book creation */
const genre = await Genre.create({
name: 'Fantasy'
})
const book = await Book.create({
title: 'Lord of the Rings',
author: 'J. R. R. Tolkien',
genreId: genre._id,
});
console.log(book)
Upvotes: 5
Reputation: 1
If I understand your question correctly I think what you're looking for is populate. https://mongoosejs.com/docs/populate.html
It would change your schema to look like the following
const bookSchema = new Schema({
title: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
genre: {
type: Schema.Types.ObjectId,
ref: 'Genre',
required: true,
}
});
const genreSchema = new Schema({
name: {
type: String,
required: true,
},
});
When you get your book you can reference the genre by doing this
Book.find()
.populate('genre')
Hopefully, that answered your question!
Upvotes: -1