The code to list out students with their associated classes and list out class with its associated students
Here’s the Node.js (Express + Mongoose) code to list:
Students with their associated classes Classes with their associated students 1️⃣ Get Students with Their Associated Classes
Student model has an enrollments field that references Enrollment, and Enrollment references Class.
app.get('/api/students-with-classes', async (req, res) => {
try {
const students = await Student.find({})
.populate({
path: 'enrollments',
populate: { path: 'class' } // Populate the class inside enrollments
});
res.json(students);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
path: 'enrollments' replaces the enrollments field with full enrollment details.
path: 'class' replaces the class field with full class details.
Explanation:
Populates their enrollments. Further populates the class field within enrollments. What does path: reference?
In Mongoose's .populate() method, path refers to the field in the schema that contains a reference to another model (i.e., a field that stores an ObjectId linking to another collection).
Example 1: Single-Level Population
Consider these schemas:
const mongoose = require('mongoose');
const StudentSchema = new mongoose.Schema({
name: String,
enrollments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Enrollment' }]
});
const ClassSchema = new mongoose.Schema({
name: String,
enrollments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Enrollment' }]
});
const EnrollmentSchema = new mongoose.Schema({
student: { type: mongoose.Schema.Types.ObjectId, ref: 'Student' },
class: { type: mongoose.Schema.Types.ObjectId, ref: 'Class' }
});
const Student = mongoose.model('Student', StudentSchema);
const Class = mongoose.model('Class', ClassSchema);
const Enrollment = mongoose.model('Enrollment', EnrollmentSchema);
Getting the students and the associated enrollments
const students = await Student.find().populate('enrollments');
Example 2: Nested Population
If you want to retrieve students along with their classes, you need nested population:
const students = await Student.find()
.populate({
path: 'enrollments', // First, populate enrollments
populate: { path: 'class' } // Then, populate the class field inside enrollments
});
First path: 'enrollments' → Populates the enrollments field in Student, replacing the ObjectIds with actual Enrollment documents. Second populate: { path: 'class' } → Looks inside each populated Enrollment and populates the class field. Example 3: Populating from Enrollment
If we query enrollments and want both the student and class:
const enrollments = await Enrollment.find()
.populate('student') // Populates student field
.populate('class'); // Populates class field
Summary
path specifies which field (that contains an ObjectId) to populate. You can nest populate to populate fields inside populated documents. .populate() is used to replace ObjectIds with actual referenced documents.
How path works with .populate() in a Student - Enrollment - Class relationship:
🖼️ How .populate() Works
🖥️ Mongoose Queries & .populate() Usage
1️⃣ Getting Students with Their Classes
const students = await Student.find()
.populate({
path: 'enrollments', // Populate enrollments field in Student
populate: { path: 'class' } // Further populate class inside enrollments
});
💡 Result:
[
{
"_id": "101",
"name": "John",
"enrollments": [
{ "class": { "_id": "301", "name": "Math" } }
]
},
{
"_id": "102",
"name": "Alice",
"enrollments": [
{ "class": { "_id": "301", "name": "Math" } }
]
}
]
2️⃣ Getting Classes with Their Students
const classes = await Class.find()
.populate({
path: 'enrollments', // Populate enrollments field in Class
populate: { path: 'student' } // Further populate student inside enrollments
});
💡 Result:
[
{
"_id": "301",
"name": "Math",
"enrollments": [
{ "student": { "_id": "101", "name": "John" } },
{ "student": { "_id": "102", "name": "Alice" } }
]
}
]
🔑 Key Takeaways
✔ path: 'enrollments' → Fetches enrollments linked to students/classes.
✔ populate: { path: 'class' } → Further fetches class inside each enrollment.
✔ populate: { path: 'student' } → Fetches student details for each class.
✔ Nested .populate() allows deep relationships!
Visualize how .populate() works in a Student - Enrollment - Class relationship.
Students Collection Enrollments Collection Classes Collection
┌──────────────────────┐ ┌────────────────────────────┐ ┌──────────────────────┐
| _id: 101 | | _id: 201 | | _id: 301 |
| name: "John" | ───────> | student: 101 | ───────> | name: "Math" |
| enrollments: [201] | | class: 301 | └──────────────────────┘
├──────────────────────┤ ├────────────────────────────┤
| _id: 102 | | _id: 202 |
| name: "Alice" | ───────> | student: 102 | ───────> | _id: 301 |
| enrollments: [202] | | class: 301 | | name: "Math" |
└──────────────────────┘ └────────────────────────────┘ └──────────────────────┘
🔄 How .populate() Works in Code
1️⃣ Getting Students with Their Classes
const students = await Student.find()
.populate({
path: 'enrollments',
populate: { path: 'class' }
});
✔ First, path: 'enrollments' replaces enrollments: [ObjectId] with actual Enrollment objects.
✔ Then, populate: { path: 'class' } replaces class: ObjectId with the actual Class object.
2️⃣ Getting Classes with Their Students
const classes = await Class.find()
.populate({
path: 'enrollments',
populate: { path: 'student' }
});
✔ First, path: 'enrollments' replaces enrollments: [ObjectId] with actual Enrollment objects.
✔ Then, populate: { path: 'student' } replaces student: ObjectId with actual Student objects.
📝 Key Takeaways
✔ .populate('field') replaces ObjectId references with actual documents.
✔ Nested .populate() lets you retrieve deeper relationships in one query.
✔ This is how you "connect" collections dynamically in MongoDB with Mongoose!
2️⃣ Get Classes with Their Associated Students
Class model has an enrollments field that references Enrollment, and Enrollment references Student.
app.get('/api/classes-with-students', async (req, res) => {
try {
const classes = await Class.find({})
.populate({
path: 'enrollments',
populate: { path: 'student' } // Populate the student inside enrollments
});
res.json(classes);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
Explanation:
Populates their enrollments. Further populates the student field within enrollments. Alternative Approach (If Enrollment Model Stores Both Student & Class References)
If your Enrollment model stores both student and class, you can query it directly:
app.get('/api/enrollments-detailed', async (req, res) => {
try {
const enrollments = await Enrollment.find({})
.populate('student')
.populate('class');
res.json(enrollments);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
Explanation:
Fetches all Enrollment records. Populates both student and class, so you get full details in one API call. Output (Example)
Students with Classes
[
{
"_id": "student1_id",
"name": "John Doe",
"enrollments": [
{ "class": { "_id": "class1_id", "name": "Math 101" } },
{ "class": { "_id": "class2_id", "name": "Physics 101" } }
]
},
{
"_id": "student2_id",
"name": "Jane Smith",
"enrollments": [
{ "class": { "_id": "class1_id", "name": "Math 101" } }
]
}
]
Classes with Students
[
{
"_id": "class1_id",
"name": "Math 101",
"enrollments": [
{ "student": { "_id": "student1_id", "name": "John Doe" } },
{ "student": { "_id": "student2_id", "name": "Jane Smith" } }
]
},
{
"_id": "class2_id",
"name": "Physics 101",
"enrollments": [
{ "student": { "_id": "student1_id", "name": "John Doe" } }
]
}
]
🔥 Key Takeaways
✔ Use .populate() to merge collections dynamically.
✔ Use Nested .populate() when dealing with indirect relationships via Enrollment.
✔ Three API Endpoints:
/api/students-with-classes → Get students & their classes /api/classes-with-students → Get classes & their students /api/enrollments-detailed → Get both in one call
Here's a breakdown front end and back end communication via RESTful apis:
This code is available at
The line app.get('/api/enrollments', async (req, res) => { defines an API endpoint that serves enrollment data. It retrieves enrollment records using Enrollment.find(), populates the associated student and class, and sends the result as JSON. This endpoint is designed to handle RESTful API queries for retrieving enrollment data. The line await fetch('/api/enrollments', { ... }) in main.js is making an HTTP request to the backend API. It retrieves enrollment data from the server and integrates it into the Browser Object Model (BOM). The fetch call is crucial for dynamically updating the frontend based on backend data. Connecting Backend and Frontend The red arrow links the API endpoint (server.js) to where it is consumed in the frontend (main.js). The blue arrow indicates that the API response is used to populate the frontend dynamically. The green arrow connects the submission of enrollment data (POST /api/enrollments) from the frontend form to the backend.