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!