Sequelize Associations(Many -to-Many)

Hi everyone! I hope everyone is doing fine. My name is Julian, and today we’re going to learn about Many-to-Many relationships in databases using an example of Students and Courses.

In real life, a student can enroll in multiple courses, and a course can have multiple students. This is an example of a Many-to-Many association.

We implement this using a junction table that connects both entities.


Conceptual Explanation

  • We have two entities: Students and Courses.
  • A student can enroll in multiple courses.
  • A course can have multiple students.
  • We need an intermediate table called StudentCourses to manage these associations.

Why Do We Need a Junction Table?

A junction table (also known as a bridge table or association table) is needed because databases do not support direct many-to-many relationships. Instead, we break it down into two one-to-many relationships.

In our case:

  • A StudentCourses table holds references to both Student ID and Course ID.
  • This allows us to track which students are enrolled in which courses.
  • Without it, we wouldn’t be able to efficiently store and retrieve the relationships.

Implementation using Node.js, Express, and Sequelize

Step 1: Install Dependencies

Make sure you have Node.js installed, then set up a project:

npm init -y

npm install express sequelize mysql2


Step 2: Setup Sequelize and Define Models

const { Sequelize, DataTypes } = require(“sequelize”);

const sequelize = new Sequelize(“schoolDB”, “root”, “password”, {

  host: “localhost”,

  dialect: “mysql”

});

const Student = sequelize.define(“Student”, {

  id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },

  name: { type: DataTypes.STRING, allowNull: false }

});

const Course = sequelize.define(“Course”, {

  id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },

  title: { type: DataTypes.STRING, allowNull: false }

});

const StudentCourses = sequelize.define(“StudentCourses”, {});

// Many-to-Many Association

Student.belongsToMany(Course, { through: StudentCourses });

Course.belongsToMany(Student, { through: StudentCourses });

sequelize.sync({ force: true }).then(() => {

  console.log(“Database & tables created!”);

});

Step 3: Creating a Controller for Managing Insertions with Try-Catch

const express = require(“express”);

const app = express();

app.use(express.json());

app.post(“/students”, async (req, res) => {

  try {

    const student = await Student.create(req.body);

    res.json(student);

  } catch (error) {

    res.status(500).json({ message: “Error creating student”, error: error.message });

  }

});

app.post(“/courses”, async (req, res) => {

  try {

    const course = await Course.create(req.body);

    res.json(course);

  } catch (error) {

    res.status(500).json({ message: “Error creating course”, error: error.message });

  }

});

app.post(“/enroll”, async (req, res) => {

  try {

    const { studentId, courseIds } = req.body; // Accept multiple course IDs

    const student = await Student.findByPk(studentId, { include: Course });

    if (!student) {

      return res.status(404).json({ message: “Student not found” });

    }

    const courses = await Course.findAll({ where: { id: courseIds } });

    if (courses.length !== courseIds.length) {

      return res.status(404).json({ message: “One or more courses not found” });

    }

    await student.addCourses(courses); // Add multiple courses at once

    const updatedStudent = await Student.findByPk(studentId, { include: Course });

    res.json({ message: “Student enrolled successfully in multiple courses”, data: updatedStudent });

  } catch (error) {

    res.status(500).json({ message: “Error enrolling student”, error: error.message });

  }

});

// Retrieve all enrolled courses for a student

app.get(“/students/:id/courses”, async (req, res) => {

  try {

    const student = await Student.findByPk(req.params.id, { include: Course });

    if (!student) {

      return res.status(404).json({ message: “Student not found” });

    }

    res.json(student);

  } catch (error) {

    res.status(500).json({ message: “Error retrieving courses”, error: error.message });

  }

});

// Retrieve all students enrolled in a course

app.get(“/courses/:id/students”, async (req, res) => {

  try {

    const course = await Course.findByPk(req.params.id, { include: Student });

    if (!course) {

      return res.status(404).json({ message: “Course not found” });

    }

    res.json(course);

  } catch (error) {

    res.status(500).json({ message: “Error retrieving students”, error: error.message });

  }

});

app.listen(3000, () => console.log(“Server running on port 3000”));

Now, you can insert students, courses, enroll students, and retrieve enrolled data via API requests using tools like Postman! 🚀


Understanding findAll() and include

What Will findAll() Return?

For Course.findAll()
[

  { “id”: 1, “title”: “Math” },

  { “id”: 2, “title”: “Science” }

  1. ]
    • Returns all courses from the Courses table.

For Student.findAll({ include: Course })
[

  {

    “id”: 1,

    “name”: “John Doe”,

    “Courses”: [

      { “id”: 1, “title”: “Math” },

      { “id”: 2, “title”: “Science” }

    ]

  }

  1. ]
    • Returns all students with their enrolled courses.

What Does include Do?

  • include: Course joins the Courses table to the Students table.
  • This fetches the related courses a student is enrolled in.

Conclusion

Today, we explored the Many-to-Many association using Students and Courses as an example. We created:

  • A junction table (StudentCourses) to handle associations.
  • Implemented Sequelize models and relationships.
  • Inserted and retrieved data.
  • Created an API controller with try-catch for error handling.
  • Understood addCourse, addCourses, removeCourse, setCourses, include, and findAll().

That’s it for today’s session! If you have any questions, feel free to ask. Happy coding!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *