How to perform a cascading deleting in MongoDB or Mongoose - node.js

I'm working on an e-commerce project in Express and MongoDB. I'm confused with architecture on how to make relationship between two models such that if I delete one element from a table, all of it's associations should be deleted. CASCADE delete, if I'm using the correct term.
I'm not a database designer, just learning Express and MongoDB. So apologies if my schema is not that good.
I have two tables (or documents in MongoDB). One is Order with schema below:
const orderSchema = new mongoose.Schema({
shippingInfo : {
type: mongoose.Types.ObjectId,
ref: 'Address'
},
user : {
type: mongoose.Types.ObjectId,
ref: 'User',
required: true
},
orderItems: [
{
type: mongoose.Types.ObjectId,
ref:'OrderItem'
}
],
totalPrice: {
type: Number,
required: true,
default: 0.0
},
status: {
type: String,
enum: ['processing', 'shipped', 'delivered','cancelled'],
default: 'processing'
},
deliveredAt: {
type: Date,
}
})
and OrderItems
const orderItemSchema = new mongoose.Schema({
product: {
type: mongoose.Types.ObjectId,
ref: 'Product'
},
name: {
type: String,
required: true
},
quantity: {
type: Number,
required: true
},
price: {
type: Number,
required: true
},
image: {
type: String,
required: true
},
})
I want if I delete an Order, all of its OrderItems should be deleted right away (using remove middleware in Order).
I know that Django has something called on_delete=model.CASCADE when we create relationships, but I'm unaware of such thing in Mongoose.
I don't want to explicitly make another API request to search for and delete all OrderItems that are referenced in orderItems array in an Order, once it is deleted. There has to be a better approach for this.
Another post on Stack Overflow suggested that in remove middleware of Order I should do something like
OrderItem.find({ order_id: this._id }).remove().exec();
That would require me to refer order_id in OrderItem right?
And this would create circular dependency since OrderItem would require Order to be created first and vice versa.
What should I do here? Should I change the schema for both tables i.e. remove orderItems entry from Order and instead add order_id in OrderItem? Or is there a Mongoose way to overcome this situation?

Related

The _id in a subdocument in mongoose schema keeps changing

I have a model like this:
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
description: {
type: String,
},
package: [
{
name: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
},
},
],
});
module.exports = product = mongoose.model('product', productSchema);
and I have created some products in my db. Everytime I fetch a product by its _id I get the package array with varying _id objects in the array. I have to use the ids of the product and a package inside it for comparison and validation purposes but I can't due to the varying nature of the package ids. Is there any way to make it static so it doesn't change just like the product document id?

Mongoose aggregate and append

I have a Mongo DB (latest version) that I am accessing with Mongoose (v6.5.4)
The project is using a discriminator pattern to keep all documents in the same collection.
There are many instances where i need to join documents.
Set up:
// Models:
const UserSchema = new Schema<IUser>(
{
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
});
// There are other similar models to <Team>
const TeamSchema = new Schema<ITeam>(
{
name: {
type: String,
required: true,
},
userIds: {
type: [Schema.Types.ObjectId],
required: true,
ref: "User",
default: [],
},
});
Problem:
I can use populate to return collections of Teams and the userIds be an array of user objects.
Where I am stuck is querying getting an array of users with an added field of teams[].
I've been trying aggregate to no success, I can loop over the users collection and return a list of Teams but this feels wrong and expensive in terms of read units (production data base is on a pay as you go service)
As data models go there is not much going for it - but it is an existing solution
Can anyone advise?
I was being stupid. The from field in my look up was wrong.
Should have been 'teams' not 'Team' which is the model name.

Mongoose reference in Collection for another Collection

I'll start with this that I'm new to backend and I was looking for some solutions for my problem but i don't know which solution will be right for my problem.
So to the point. I'm creating a pizza restaurant project in Next.js with Mongoose and simple api. I have for example collections : Product (here will be my pizzas) and the code for this model is below.
import mongoose from "mongoose";
const ProductSchema = new mongoose.Schema
{
title: {
type: String,
required: true,
maxlength: 60,
},
desc: {
type: String,
required: false,
maxlength: 200,
},
img: {
type: String,
required: false,
},
prices: {
type: [Number],
required: true,
},
extraOptions: {
type: [
{
text: { type: String, required: true },
price: { type: Number, required: true },
},
],
},},); export default mongoose.models.Product || mongoose.model("Product", ProductSchema);
In this Schema i have an array of extraOptions ( for example extra cheese for 1$ and extra onion for 1$) but I want to adopt the principle that all products can have the same additional additives. It is a pity to prescribe the same additives for each products ( each pizza)
So, can I create a new model of extraOptions for pizzas and create some 'reference' (like in SQL but in Mongo) for collections Product? For example my simple model of extraOptions :
import mongoose from "mongoose";
const extraSchema = new mongoose.Schema(
{
title: {
type: String,
required: true,
maxlength: 60,
},
price: {
type: Number,
required: true,
},
},
);
export default mongoose.models.ExtraOptions || mongoose.model("ExtraOptions", extraSchema);
How could I create a maybe reference in the products collection to display there all documents of the additional extra options ? I was reading about CopyTo method, populate method and subdocuments but i don't know which is solution for me and how can i Use it... Thanks for all answers and sorry if i wrote epic here.
Maybe some extraOptions: [{type:mongoose.Schema.Types.ObjectId,ref:'extraSchema'}],or i dont really know. Thanks a lot for help
It is a pity to prescribe the same additives for each products ( each
pizza) So, can I create a new model of extraOptions for pizzas and
create some 'reference' (like in SQL but in Mongo) for collections
Product?
Your assumption is correct - there is no need to store the same data of additives in each one of your products. Indeed, the right approach is to extract the additives data to a different collection, and reference the relevant additives for each product.
This way, your ProductSchema will look like this (for brevity I excluded the 'required: false' field since false is the default value):
import mongoose from "mongoose";
const ProductSchema = mongoose.Schema({
title: {
type: String,
required: true,
maxlength: 60,
},
desc: {
type: String,
maxlength: 200,
},
img: String,
prices: {
type: [Number],
required: true,
},
extraOptions: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'ExtraOptions'
}],
});
export default mongoose.models.Product || mongoose.model("Product", ProductSchema);
In this way, when you get some products documents, the 'extraOptions' property will contain the id's of the extraOptions ducements.
If you want to retrieve the data of the additives itself, instead of just the id, you can use the populate method of mongoose:
Product.find().populate('extraOptions');
For further learning about schema design in mongoDB (it might help you decide in general whether to reference or embed), check this video

MongoDB database design for products and bundles

I am trying to build an e-commerce website based on Node.js with a mongoDB database and I am encountering problems about some database design or some logic I am missing
To sum up, I have Product that contain price, name, description etc... and Bundle that contains an array of products (by reference). The main problem come when I have to order, I can't get Product AND Bundle together ...
So I have already a Product schema :
const productSchema = new mongoose.Schema({
file: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
preparation: String,
allergics: {
type: Array,
required: true,
},
price: {
type: Number,
required: true,
},
// More fields
});
module.exports = mongoose.model('Product', productSchema);
And a Bundle schema that contains ref to Product (A bundle contains multiple products) :
const bundleSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
},
itemsId: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true,
}],
description: String,
reduction: {
type: Number,
min: 0,
default: 0,
max: 100,
},
});
module.exports = mongoose.model('Bundle', bundleSchema);
So when a user orders a bundle OR a single product, I use this schema :
const orderSchema = new mongoose.Schema({
orderedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
articlesId: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
},
],
itemsNumber: {
type: Array,
required: true,
},
amount: Number,
orderedAt: Date,
placeToShip: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Place',
},
});
module.exports = mongoose.model('Order', orderSchema);
As you can see, I only reference to Product , but I want to reference to Product AND Bundle , I don't know if this is possible, or if this is the wrong way to design the database like that.
Sorry if the post is a bit long, but I am trying to be as clear as possible! Thanks a lot.
if you want to reference product or bundle(depending on user buys bundle or single product) in articleId, you can do it like this:
Dont give ref in the articleId field of your orderSchema, just specify its type as ObjectId.
const orderSchema = new mongoose.Schema({
...
articlesId: [
{
type: mongoose.Schema.Types.ObjectId
},
],
...
});
And, while populating tell it which model to populate from.
//In case user bought a product
Order.find({findQuery})
.populate({path : '',model : 'Product'})
.exec(function(err,result){...});
//In case user bought a Bundle
Order.find({findQuery})
.populate({path : '',model : 'Bundle'})
.exec(function(err,result){...});
But, you must have a way to find out user bought a single product or a bundle.
Hope that helps you!

MONGODB MULTI PARAMETER SEARCH QUERY

I have the following schema:
var ListingSchema = new Schema({
creatorId : [{ type: Schema.Types.ObjectId, ref: 'User' }],//LISTING CREATOR i.e. specific user
roommatePreference: { //preferred things in roommate
age: {//age preferences if any
early20s: { type: Boolean, default: true },
late20s: { type: Boolean, default: true },
thirtys: { type: Boolean, default: true },
fortysAndOld: { type: Boolean, default: true }
},
gender: {type:String,default:"Male"}
},
roomInfo: {//your own location of which place to rent
address: {type:String,default:"Default"},
city: {type:String,default:"Default"},
state: {type:String,default:"Default"},
zipcode: {type:Number,default:0},
},
location: {//ROOM LOCATION
type: [Number], // [<longitude>, <latitude>]
index: '2d' // create the geospatial index
},
pricing: {//room pricing information
monthlyRent: {type:Number,default:0},
deposit: {type:Number,default:0},
},
availability:{//room availability information
durationOfLease: {
minDuration: {type:Number,default:0},
maxDuration: {type:Number,default:0},
},
moveInDate: { type: Date, default: Date.now }
},
amneties : [{ type: Schema.Types.ObjectId, ref: 'Amnety' }],
rules : [{ type: Schema.Types.ObjectId, ref: 'Rule' }],
photos : [{ type: Schema.Types.ObjectId, ref: 'Media' }],//Array of photos having photo's ids, photos belong to Media class
description: String,//description of room for roomi
status:{type:Boolean,default:true}//STATUS OF ENTRY, BY DEFAULT ACTIVE=TRUE
},
{
timestamps:true
}
);
The application background is like Airbnb/Roomi app, where users can give their rooms/places on rent. Now i want to implement a filter for a user finding the appropriae listing of room.
Here creatorId, rules, amneties are refIds of other schemas. I want to write a query which will give me listings based on several parameters,
e.g. user can pass rules, pricing info, some amneties, gender etc in req queries.
The query parameters depends upon user's will.
Is there any way to do nested query like thing for this?, like the way we did in SQL.
Well, mongodb is not made to be used as relational DB.
instead, i would suggest transforming amenities array into an array of objects with the amenities embeded inside the Listings schema.
so you can query as follows:
// Schema
ListSchema = mongoose.Schema({
....
amneties: [{aType: 'shower'}]
// or you can make it a simple array of strings:
// amneties: ['shower']
....
})
// query
Listings.find({'amneties.aType' : <some amenity>})
there are no joins in mongodb, you can still make "joins" as mongoose calls them populate, but they are happening on your server, and every populations requires a round trip to the server.
if you still wish to use references to the amneties collection, you should query it first and populate the Listing object on them.

Resources