- Explain the purpose of Mongoose middleware. How can you use pre and post hooks to manage data before and after certain operations? Provide examples of use cases where middleware would be beneficial in an Express.js application.
- How does Mongoose support indexing, and why is indexing important for database performance? Discuss how to create and use indexes in your schema and the impact they have on query efficiency.
- Describe the concept of schema methods in Mongoose. How do they differ from static methods, and when would you use each type? Give examples of scenarios where schema methods would enhance the functionality of your application.
- What are the advantages of using Mongoose’s built-in query helpers? How do they simplify the process of writing queries in an Express.js application? Discuss examples of custom query helpers you might create for a project.
- How does Mongoose handle relationships between different collections? Discuss the use of references and embedded documents, and compare their advantages and disadvantages in different scenarios.
-
-
Save halitbatur/1d6bcff6f1eb5b78792d5402a9a20311 to your computer and use it in GitHub Desktop.
With @NtokozoMitchell and @Letagoeve
- Mongoose middleware allows you to execute functions before or after certain operations such as validation, saving, or removing data in MongoDB. By using pre and post-hooks, you can intercept and modify data before it is saved or after the operation is completed. Middleware in an Express.js application can benefit tasks like logging requests, authentication, or error handling.
- Mongoose supports indexing by allowing indexes to be defined on schema fields, optimizing query performance. Indexes improve query speed, and sorting efficiency, and reduce disk I/O operations. They can be defined directly in the schema definition or using the schema. index method. Examples include unique indexes and compound indexes. Indexes significantly enhance query efficiency by allowing faster query execution, efficient range queries, and improved sort performance. Proper indexing leads to faster responses and more efficient resource usage in databases.
- Mongoose schema methods enable you to add custom functionality to documents by attaching functions to the schema. Unlike static methods, which are tied to the model, schema methods are linked to individual documents and can access and modify their properties. This makes them perfect for tasks that rely on document data, such as validation, calculations, and event triggers. In contrast, static methods are better suited for utility functions that don't depend on document data. By leveraging schema methods, you can keep your code organized, reusable, and closely tied to your data model, leading to a more robust and maintainable application.
- Mongoose's built-in query helpers simplify query logic by making it reusable and readable, which enhances code maintainability. In an Express.js application, they streamline complex query construction, reducing boilerplate code. Custom query helpers, like filtering by active status, pagination, and keyword search, encapsulate common operations. These helpers allow developers to write more concise and maintainable queries. This results in efficient and readable code tailored to specific application needs.
- Mongoose handles collection relationships via references (storing related doc _ids) and embedded documents (storing related docs within another). References suit large datasets and complex relationships, while embedded documents suit small, frequently accessed data and simple relationships. Choose wisely to ensure data consistency and efficient querying.
Nokulunga, Konanani, Koketso
- Mongoose middleware allows you to run functions before or after certain database operations on Mongoose documents. It's useful for :
Data Validation/ Modification - Ensures that data is correct or transforming it before saving or updating.
Logging/Auditing - Keeping track of changes to data
Asynchronous Operations - Performing tasks like cleanup before deleting data.
Error Handling - Managing errors consistently.
You can use pre and post hooks in Mongoose to manage data before and after certain operations like saving, updating, or deleting documents.
Middleware in an Express.js application is used to handle requests and responses efficiently.
Usecases:
a) Authentication and Authorization - Middleware can check if a user is authenticated.
b) Logging - Middleware can log details of each request for monitoring and debuging puporses.
c) Body Parsing - Middleware can parse incoming request bodies before handlers process them
d) Error Handling - Middleware can catch and send uniform response.
e) Rate Limiting - Limit the number of requests from a single IP to prevent abuse.
f) CORS(Cross- Origin Resource Sharing) - Allows or restricts resource sharing between different origins.
g) Request Compression - Compress responses to reduce size and increase speed.
h) Request Validation - Validates incoming requests to ensure they meet a certain criteria.
-
Mongoose enables you to define indexes directly in your schema, which greatly enhances database performance. Indexing allows MongoDB to quickly find documents based on specific fields, rather than scanning the entire collection. By setting index: true in your schema fields, you can create indexes on fields like usernames or email addresses, making data retrieval more efficient.
-
Schema methods are functions defined on the schema that become available on each document instance created from that schema in contrast to static methods which are available directly on the model schema . These methods enable you to add custom behaviors to your document instances, making them particularly useful for operations that need to be performed on individual documents. examples : Custom Queries:
Find users by age. Aggregation:
Calculate the average age of all users. Bulk Operations:
Update the status of all orders to 'Processed'.
You can also create compound indexes, which combine multiple fields, to optimize complex queries. Indexes not only speed up data access but also improve sorting and enforce uniqueness. By minimizing the need for MongoDB to search through every document, indexes significantly improve query efficiency and application responsiveness.
- Mongoose's query helpers provide a simpler way to interact with MongoDB by wrapping its complex syntax into easy-to-use methods like find, findOne, update, and delete. This abstraction improves code readability, enables code reuse across projects, encourages best practices, and accelerates development by allowing developers to focus on application logic. Additionally, these helpers integrate seamlessly with Express.js middleware, enabling efficient and streamlined database operations.
custom examples:
// Define a custom query helper to find documents created within a specific date range
userSchema.query.byDateRange = function(startDate, endDate) {
return this.where({ createdAt: { $gte: startDate, $lte: endDate } });
};
// Usage example:
const startDate = new Date('2023-01-01');
const endDate = new Date('2023-12-31');
const usersCreatedInRange = await User.find().byDateRange(startDate, endDate);
// Define a custom query helper to find documents based on status
userSchema.query.byStatus = function(status) {
return this.where({ status });
};
// Usage example:
const activeUsers = await User.find().byStatus('active');
- Mongoose provides two main ways to handle relationships between different collections: references and embedded documents.Embedded documents store related data directly within a parent document. This means the related information is nested inside the main document.
Advantages of Embedded Documents
Faster Queries: All related data is fetched in a single query, making data retrieval quicker.
Simpler Code: Easier to manage because all related data is in one document.
Atomic Operations: Changes to the parent and embedded documents happen together, keeping data consistent. Disadvantages of Embedded Documents
Data Duplication: Repeating the same data in multiple documents.
Limited Scalability: Not good for large data or complex relationships due to size limits (16 MB per document in MongoDB).
Harder to Update: Changing an embedded document means updating the whole parent document, which can be slow and inefficient.
Thabiso
Vuyo
Katleho
- Mongoose middleware allows you to define additional logic that you want to occur automatically before or after certain database operations. Pre middleware functions run before the hooked method , they are useful for performing operations such as validation, modification of the document, or logging. Post middleware functions run after the hooked method has completed. They are often used for password hashing, sending notifications, or triggering other side effects.
- Indexes allow the database to search and get data quickly without scanning the entire collection in the database, in large databases this is important for performance.
userSchema.index({ username: 1, email: 1 });
- In mongoose, schema methods are used to define instance specific functions that operate on the entire collection. Schema methods enhance functionality by encapsulating document-specific logic(e.g. password comparison) while static methods streamline operations across multiple docs (e.g. querying by role)
4.Using mongoose built in query helpers in express.js apps offer several advantages, primarily enhancing code readability, maintainability and scalability. These encapsulate complex query logic into reusable functions, reducing redundancy and potential errors in query construction. This approach not only simplifies the implementation of MongoDB queries but also facilitates testing and enhances the overall efficiency of data access operations in mongoose based apps.
5.Mongoose handles relationships through embedding and referencing.
Embedding: This is achieved by putting data that is related in a single document. An example would be embedding a user collection in a post collection
Referencing: When we reference we would normally reference a user collection in a post collection by referencing the user id. This would reference a related data in a separate document.
Advantages:
Embedding: Atomic Operations: Since all related data is in one document, updates to this document are atomic, ensuring data consistency.
Referencing: Flexibility: Allows for more flexible and normalized data models, making it easier to manage complex relationships.
Disadvantages:
Embedding: Redundancy: Duplicating data across multiple documents can lead to redundancy and increased storage requirements.
Referencing: Consistency: Maintaining consistency across referenced documents can be challenging, especially without transactions.
Use cases:
Embedding:
One-to-One Relationships: Where one document directly relates to another (e.g., user profile and user settings).
One-to-Many Relationships: Where the “many” side of the relationship is not excessively large and is frequently accessed with the parent document (e.g., an order with multiple line items).
Referencing:
Many-to-Many Relationships: Where data is highly interconnected and frequently changes (e.g., students and courses).
Large Subdocuments: Where the subdocuments are large or frequently accessed independently
(e.g., blog posts and comments).
Hophney Lentsoana
Lethukuthula Mkhonto
Pumlani Kewana
Query middleware in Mongoose helps intercept and modify queries before or after they are executed. This allows you to add extra conditions, modify the results, or perform other tasks related to the query process.
With “Pre Hooks,” you can customize actions before they happen, like validating data or making changes. “Post Hooks” lets you perform additional tasks after an action, such as sending emails or processing data. These hooks are essential for building reliable and efficient applications.
Middleware in an Express.js application can be incredibly beneficial for various use cases, providing a way to execute code, make changes to the request and response objects, end the request-response cycle, and call the next middleware function in the stack.
Use Cases
- Logging Requests
- Authentication and Authorization
- Error Handling
Indexing in Mongoose and Its Importance
Support and Importance:
Mongoose Indexing: Mongoose allows indexing to speed up database queries.
Importance: Indexes enhance query speed, reduce resource usage, and handle large datasets efficiently.
creating indexes
To create indexes in Mongoose, you specify them in your schema definition. This can be done through schema options like unique and index for individual fields, or using the index method for compound indexes involving multiple fields.
Schema methods, also known as instance methods, are functions that are specified on specific Mongoose model document instances. They are employed for data retrieval or manipulation from a single document. For example, formatting a user's full name.
Static Methods: These are functions that are defined on the model rather than on specific cases or individual instances. They are used for operations that involve multiple documents or the entire collection. For example, finding users by email.
Benefits of Using the Built-In Query Helpers in Mongoose
- Code Simplified: Code is streamlined using query helpers, which improves readability and concision.
- Reusable Logic: To encourage reuse throughout the program, encapsulate common query logic.
- Chainable Queries: Provide a simple, fluid way to chain query methods together to create more complicated queries.
- Improved Readability: Offer simple techniques that make it simpler to comprehend and update queries.
- Customization: Mongoose models can be easily extended with unique query methods to meet the demands of certain applications.
Mongoose's built-in query helpers simplify writing queries in an Express.js application by:
- Streamlining Code: Reduce boilerplate and repetitive code.
- Encapsulating Logic: Embed common query patterns, making code more modular.
- Chaining Methods: Allow for clean, readable, and maintainable query chains.
- Enhancing Readability: Provide intuitive, easy-to-understand query methods.
Mongoose handles relationships between different collections in MongoDB primarily through two mechanisms: References (Population) and Embedded Documents.
References
With references, Mongoose stores the ObjectId of one document within another document. This is akin to foreign key relationships in SQL databases. You can then use the populate method to pull in the referenced documents.
Embedded Documents
In this approach, documents are embedded directly within other documents. This can be more efficient for certain read operations because all related data is stored in a single document, but it can lead to larger document sizes and potential issues with the BSON document size limit (16MB).
When to Use References:
- Normalized Data: Useful when data is highly normalized.
- Large Data Sets: When documents contain large amounts of data that don't need to be loaded all at once.
- Many-to-Many Relationships: Easier to manage many-to-many relationships.
When to Use Embedding:
- Denormalized Data: Useful when data is denormalized for faster read operations.
- Frequent Reads: When the relationship data is frequently read along with the parent document.
- One-to-Few Relationships: Suitable for one-to-few relationships where the embedded documents are limited in number and size.
Session Members
@Yenkosii
@Geraldmutsw
@samuelthis
- Mongoose middleware assists when it comes to controlling and managing data more effectively, to ensure and make your application more reliable as well as secure.
Pre-Hooks, are there to check data before it gets stored in the database. Such as social networking apps, A pre-save hook on your user model will automatically encrypt passwords before they are stored. These are Pre-hooks actions before data changes.
For Blogging a post-save hook on the comment model could send an email to the author when new comments are posted. These are Pre-hooks actions after data changes.
Examples of use cases where middleware would be beneficial in an Express.js application
Middleware: keeps your logic organised and separate from your main route handlers, which makes code clean and easy to read.
Data Validation: Pre-save hooks ensure your data meets specific criteria before data are stored.
Automation: Post-hooks could trigger actions like sending emails, and updating other features of the application.Custom Logic: You can tailor middleware to your specific business rules and application needs.
- Indexing, a core concept in mongoDB. It significantly improves query performance by acting like catalogs for mongoDB collections. They organize data based on specific fields, allowing for faster retrieval. You define indexes within your mongoose schema using the index option and it also creates indexes automatically on the I.D field by default.
Indexes work best with queries that use equality comparisons (=) or range comparisons (<, >) on the indexed fields. When a query uses an indexed field, MongoDB can quickly locate relevant documents without scanning the entire collection. This dramatically improves query execution speed.
- Mongoose schema methods offer a way to extend the functionality of your Mongoose documents (instances of your schema). They differ from static methods in their scope and purpose
- Schema Methods (Instance Methods)
These methods are accessible on individual document instances created from your schema and they are used to perform operations specific to a particular document, often manipulating its data or interacting with related data. For an example you can create methods to perform calculations, formatting, or validation specific to a document's fields and also defining methods to fetch or manipulate data from related collections based on the document's properties
- Query helpers allow you to define custom methods on Mongoose queries, making them more semantic and enabling the use of chaining syntax.
Query helpers simplify writing queries in an Express.js application by enhancing readability through method chaining, promoting reusability by encapsulating common query logic, and ensuring consistent query patterns across the application.Creating custom query helpers can significantly streamline and enhance the efficiency of your queries in a Mongoose-based project. Here are a few examples.
Active Records Helper
A helper to filter records based on their active status.
schema.query.active = function() {
return this.where({ status: 'active' });
};
usage: const activeUsers = await User.find().active().exec();
Recent Records Helper
A helper to sort records by creation date, showing the most recent first.
schema.query.recent = function() {
return this.sort({ createdAt: -1 });
};
Usage: const recentPosts = await Post.find().recent().exec();
Pagination Helper
This helper paginates results by specifying the page number and the number of items per page.
schema.query.paginate = function(page, limit) {
const offset = (page - 1) * limit;
return this.skip(offset).limit(limit);
};
By Category Helper
This helper filters records by a specified category.
schema.query.byCategory = function(category) {
return this.where({ category: category });
};
Usage: const techPosts = await Post.find().byCategory('Technology').exec();
By Author Helper
This helper filters records by a specific author.
schema.query.byAuthor = function(authorId) {
return this.where({ author: authorId });
};
Usage:
const userPosts = await Post.find().byAuthor(userId).exec();
- Mongoose manages relationships between collections using references and embedded documents. Each method has its own pros and cons.
References (Population)
Definition: Stores the ObjectId of one document inside another document, similar to foreign keys in relational databases. The populate method retrieves referenced documents.
Advantages:
- Efficient for large datasets or one-to-many relationships.
- Avoids data duplication.
Disadvantages:
- Requires extra queries to fetch related data (using populate).
- Can be slower for complex relationships.
Embedded Documents: Store the entire related document directly within another document. Like having a smaller document nested inside a larger one.
Advantages:
- Faster access to related data as it's already fetched.
- Good for small, self-contained data that is often accessed together.
Disadvantages:
- Can lead to large documents and slower updates if the embedded data changes frequently.
- Less flexible for complex relationships or one-to-many scenarios.
Team Members (@phamela, @lindokuhle and @ImisebenziEmihle )
ANSWERS.
- Mongoose middleware (hooks) are functions that run before or after certain Mongoose operations, like save, validate, or remove.
Pre Hooks: Run before an operation. For example, to hash a password before saving:
Use Cases:
Validation: Check data before saving (e.g., unique email).
Data Transformation: Modify data (e.g., hash passwords).
Logging and Auditing: Track changes (e.g., log deletions).
Notifications: Trigger events (e.g., send welcome emails).
Middleware helps keep your code organized and maintainable.
- Mongoose Indexing:
Purpose: Indexes improve query performance by allowing faster data retrieval. Mongoose automatically creates indexes for defined fields when your app starts.
Impact: Indexes speed up read operations but slightly slow down writes. Disable auto-index creation in production to avoid performance impact.
Creating Indexes: Define an index in your schema using { type: [String], index: true }. For example, indexing hashtags for faster queries on that field.
Query Efficiency: Indexes help with filtering, sorting, and searching.Use dot notation to query embedded data within documents.
- Schema Methods:
Purpose: Functions defined on Mongoose schemas that operate on individual documents.
Use Cases: Ideal for document-specific operations like formatting data or checking validity within a single instance.
Static Methods:
Purpose: Functions defined on Mongoose schemas that operate on the entire model.
Use Cases: Useful for model-wide tasks like querying data across all documents or performing aggregations.
Example Scenarios:
Schema Methods: Formatting user data before saving.
Static Methods: Finding all users who signed up in the last month.
- Advantages:
Simplify Queries: They make complex database queries easier to write and understand.
Reuse Code: You can create custom functions for common queries, reducing repetition in your Express.js application.
Examples:
Custom Query Helpers: Like finding active users or searching by name, making queries more concise and readable.
Custom Query example :
Suppose you want to find all active users. Define a custom query helper like this:
JavaScript
// models/user.js
userSchema.query.activeUsers = function () {
return this.where({ isActive: true });
};
//Usage in routes/users.js
const activeUsers = await UserModel.find().activeUsers();
Find all users with a specific role: User.find().byRole('admin').
Retrieve articles by category: Article.find().byCategory('technology')
- Embedded Documents:
What Are They?: In an embedded data model, related data is stored
References:
What Are They?: References store relationships by including links (usually ObjectIDs) from one document to another, stored in separate collections.
Advantages:
Normalized Data Models:
Data is divided into multiple collections, avoiding duplication.
Complex Relationships:
Suitable for many-to-many relationships or hierarchical data.
Independent Queries:
Related entities can be queried independently.
Use Cases:
When embedding would lead to excessive data duplication.
Complex relationships or frequent queries on related data.
Considerations:
Read Performance: References may not provide significant read advantages over embedding.
Data Consistency: Carefully manage data consistency when using references
Choosing Between Them:
Embedded Documents:
Use when data duplication is acceptable and read performance matters.
Ideal for one-to-one or one-to-many relationships.
References:
Choose for complex relationships, many-to-many scenarios, or frequent independent queries.
Consider the trade-offs based on your application’s specific needs.
Mpho and Wesley
- Mongoose middleware lets you run functions before or after database operations, making it easier to manage data in your Mongoose models. This is especially useful in Express.js apps for tasks like
validation: if (!isValidEmail(user.email)) {
return next(new Error('Invalid email format'));
},
password hashing: if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 10);
}
next();
logging: userSchema.post('remove', function(doc) {
console.log(User "${doc.username}" has been removed.
);
});
2)Indexes allow the database to quickly locate and access the data without scanning the entire collection. Unique indexes ensure that the indexed fields do not have duplicate values.
index method: userSchema.index({ username: 1 });
unique index: userSchema.index({ email: 1 }, { unique: true });
3)Schema methods are instance methods that operate on individual document instances. They are defined on the schema and can be called on any document created from that schema. Static methods are useful for operations that apply to the entire collection of documents,
4)Advantages:
Code Reusability, Cleaner Code, Enhanced Readability.
Mongoose query helpers simplify the process of writing complex queries by abstracting common patterns and logic. This makes it easier to manage and maintain the code, especially as the application grows. examples:
Filtering by Active Status- userSchema.query.byActiveStatus = function(isActive) {
return this.where({ isActive: isActive });
};
5)References-storing the ObjectId of the related document in a field, and then using Mongoose's populate method to retrieve the referenced documents.
Embedded Documents- related data is embedded directly within the parent document. This is done by nesting subdocuments inside a document.
advantages:
Normalization: Avoids data redundancy by storing related data in separate collections.
Query Efficiency: Fetching related documents can be done dynamically with the populate method.
disadvantages:
Performance: Multiple database queries are needed to retrieve referenced documents, which can be slower for large datasets.
Complexity: More complex queries and additional handling for populate operations
Sinethemba Zulu
Tumelo Thinane
Nonhlanhla Mazibuko
- Mongoose middleware serves as a mediator, intercepting and modifying operations within your Node.js application.
Pre-hooks allow you to execute custom code before a certain operation, such as saving or updating data, ensuring data validation.
Post-hooks are employed to handle actions after a specific operation, like sending notifications, logging changes, updating associated resources.
use cases: Authentication and Authorization : Use pre hooks to hash passwords before saving user data to the database and Use post hooks to log successful login attempts.
- Indexing is crucial for database performance as it helps in speeding up data retrieval operations by making the query process more efficient. By using indexes, databases can locate and fetch relevant data more quickly, resulting in quicker response times for queries.
` const mongoose = require('mongoose');
// Define your schema
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
createdAt: { type: Date, default: Date.now }
});
// Create an index on the email field
userSchema.index({ email: 1 }, { unique: true });
// Create the User model
const User = mongoose.model('User', userSchema); `
- in Mongoose, schema methods are functions that you can define on a schema. These methods are then available on the model instances (documents) created from that schema.
Schema methods are useful for encapsulating custom logic or behavior that you want to associate with your documents. They allow you to add custom functionality to your models, making your code more modular and maintainable.
an example of scenarios when to use the different scenarios
Static Methods:
User management: findAllUsers
, createUser
, updateUser
, deleteUser
Batch operations: bulkUpdate
, bulkDelete
Data aggregation:getAverageRating
,getTotalSales
Instance Methods:
Validations: validatePassword
, validateUniqueEmail
Document-level operations: generateUUID
, calculateDerivedValue
Formatting/Transformation: formatName,
toPublicJSON`
-
Separation of concerns- separates data fetching logic from the rest of the application which makes the codebase more readable and easy to maintain. Mongoose built-in query helper allow us to define reusable code logic, which avoids code duplication, making the application less complex. Makes the code more readable by enclosing complex query logic within meaningful method names which allows us to reapply that logic across all queries. by encapsulating query logic, improving code readability and reusability, supporting method chaining, ensuring consistent business logic application, and simplifying unit testing. We can create a custom query helper to encapsulate some code logic that filters the data in some specified condition.
-
Mongoose provides two main ways to handle relationships between different collections: references and embedded documents.
References:
Store ObjectIds to represent relationships
Flexible, scalable, but require additional queries
Embedded Documents:
Nested subdocuments in a single document
Fast data retrieval, ensure data consistency
Limited flexibility, size constraints
Choose based on:
One-to-one/many: Embedded better
Many-to-many: References better
Frequently accessed data: Embedded better
Large/growing data: References better
Data consistency needs: Embedded better
Nhlanhla, Mpilo and Angela
Mongoose middleware, which is also known as hooks, allows developers to execute functions before or after certain operations, such as saving, validating, or removing documents. Pre hooks can be used to validate or modify data before saving it to the database, while post hooks can perform actions like logging or cleanup after an operation completes. For example, a pre-save hook could hash a user's password before storing it, and a post-save hook might send a confirmation email. Middleware is beneficial in an Express.js application for maintaining data integrity, automating repetitive tasks, and enhancing security.
Mongoose supports indexing to improve database performance by allowing faster retrieval of documents. Indexes are created on fields that are frequently queried, which speeds up search operations. You can define indexes in your schema using the index method or schema options. For instance, { name: { type: String, index: true } } creates an index on the name field. Indexing is crucial for large datasets, as it reduces the time complexity of queries, making them more efficient and responsive.
Schema methods in Mongoose are instance methods defined on the schema. They are used to add functionality to individual documents created with that schema. In contrast, static methods are defined on the schema and operate on the model as a whole rather than individual instances.
Schema Methods:
Operate on individual document instances.
Useful for tasks related to a specific document, such as manipulating or formatting data within that document.
Static Methods:
Operate on the model itself.
Useful for tasks involving multiple documents or querying the database.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
Examples of this are below:
const userSchema = new Schema({
username: String,
email: String,
password: String
});
// Schema method to greet a user
userSchema.methods.greet = function() {
console.log(
Hello, ${this.username}!
);};
const User = mongoose.model('User', userSchema);
// Example usage
const user = new User({ username: 'Alice' });
user.greet(); // Outputs: Hello, Alice!
Code Reusability: Encapsulate commonly used query logic, reducing repetition.
Readability: Make queries more readable and expressive.
Maintainability: Centralize query logic, making it easier to update and maintain.
Simplification in Express.js:
Query helpers allow you to define complex queries in a single place, making your route handlers cleaner and simpler.
5.Mongoose handles relationships between collections using:
References (Population):
Definition: Use ObjectId to link documents from different collections.
Advantages:
Keeps collections normalized and avoids data duplication.
Suitable for relationships where data is frequently updated or has many-to-many relationships.
Disadvantages:
Requires additional queries (population) to fetch related data, which can impact performance