Sequelize Associations

Understanding Associations in Sequelize

Hi everyone! I hope you’re doing well. Today, let’s talk about a common challenge in database design and how we can solve it using Sequelize associations. If you’re new to databases and ORM (Object-Relational Mapping), don’t worry! We’ll break it down step by step so that it’s easy to understand.


The Problem: A Real-Life Scenario from My Previous Firm

When I was working at my previous firm, we managed an educational platform where students could enroll in multiple courses. Initially, we stored course names directly in the student’s table as a comma-separated string. However, this approach quickly became problematic:

  • Data Duplication: The same course name appeared multiple times for different students.
  • Difficulty in Querying: Searching for students in a particular course was slow and inefficient.
  • Data Integrity Issues: If a course name changed, we had to update multiple records manually, increasing the risk of inconsistencies.

This led to frequent bugs and inefficiencies, making database maintenance a nightmare. Thankfully, Sequelize associations came to our rescue by providing a structured way to manage these relationships.


Solution: Using Associations in Sequelize

Sequelize provides different types of associations to structure your data efficiently:

  • One-to-One (1:1): A student has one identity card.
  • One-to-Many (1:M): A student can enroll in multiple courses.
  • Many-to-Many (M:M): Students can enroll in multiple courses, and each course can have multiple students.

We’ll go step by step, coding each relationship in Sequelize, so you can grasp the concepts, apply them, and then move on to the next.


Step 1: Setting Up Sequelize and Database Connection

Before we start defining our models, let’s install Sequelize and SQLite (or any other database you prefer):

npm install sequelize sqlite3

Then, create a database connection file:

utils/db-connection.js

const { Sequelize } = require(‘sequelize’);

const sequelize = new Sequelize({

    dialect: ‘sqlite’,

    storage: ‘./database.sqlite’

});

module.exports = sequelize;


Step 2: One-to-One Relationship

What is One-to-One?

A one-to-one relationship means that each record in one table corresponds to exactly one record in another table. For example, in our platform, a student has a single identity card.

How We Will Implement It:

  1. Define the Student model.
  2. Define the IdentityCard model.
  3. Establish the one-to-one relationship between them.
  4. Sync the database and test the association.
  5. Query data to retrieve associated records.
  6. Implement a controller to handle CRUD operations.

In this document, we will cover Sequelize associations with real-world scenarios, including:

  • One-to-One
  • One-to-Many
  • Many-to-Many

Each section will include:

  • Model definitions
  • Controller implementations
  • Example payloads
  • Queries to retrieve data

1. Project Structure

project-folder/

│– models/

│   │– student.js

│   │– identityCard.js

│   │– teacher.js

│   │– course.js

│   │– studentCourse.js

│   │– index.js

│– controllers/

│   │– studentController.js

│   │– teacherController.js

│   │– courseController.js

│– app.js

│– database.js


2. One-to-One Association (Student & IdentityCard)

models/student.js

module.exports = (sequelize, DataTypes) => {

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

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

    name: DataTypes.STRING,

  });

  return Student;

};

models/identityCard.js

module.exports = (sequelize, DataTypes) => {

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

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

    cardNumber: DataTypes.STRING,

  });

  return IdentityCard;

};

models/index.js (Associations)

const Student = require(“./student”);

const IdentityCard = require(“./identityCard”);

Student.hasOne(IdentityCard, { foreignKey: “studentId” });

IdentityCard.belongsTo(Student, { foreignKey: “studentId” });

module.exports = { Student, IdentityCard };

controllers/studentController.js

const { Student, IdentityCard } = require(“../models”);

exports.createStudentWithCard = async (req, res) => {

  try {

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

    const identityCard = await IdentityCard.create({

      …req.body.identityCard,

      studentId: student.id,

    });

    res.status(201).json({ student, identityCard });

  } catch (error) {

    res.status(500).json({ error: error.message });

  }

};

Payload Example

{

  “student”: { “name”: “John Doe” },

  “identityCard”: { “cardNumber”: “1234567890” }

}


3. One-to-Many Association (Teacher & Students)

models/teacher.js

module.exports = (sequelize, DataTypes) => {

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

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

    name: DataTypes.STRING,

  });

  return Teacher;

};

models/index.js (Associations)

const Teacher = require(“./teacher”);

const Student = require(“./student”);

Teacher.hasMany(Student, { foreignKey: “teacherId” });

Student.belongsTo(Teacher, { foreignKey: “teacherId” });

module.exports = { Teacher, Student };

controllers/teacherController.js

const { Teacher, Student } = require(“../models”);

exports.createTeacherWithStudents = async (req, res) => {

  try {

    const teacher = await Teacher.create(req.body.teacher);

    const students = await Student.bulkCreate(req.body.students.map(stu => ({ …stu, teacherId: teacher.id })));

    res.status(201).json({ teacher, students });

  } catch (error) {

    res.status(500).json({ error: error.message });

  }

};

Payload Example

{

  “teacher”: { “name”: “Alice” },

  “students”: [ { “name”: “John” }, { “name”: “Emma” } ]

}


4. Many-to-Many Association (Students & Courses)

models/course.js

module.exports = (sequelize, DataTypes) => {

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

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

    title: DataTypes.STRING,

  });

  return Course;

};

models/studentCourse.js (Junction Table)

module.exports = (sequelize, DataTypes) => {

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

    grade: DataTypes.STRING,

  });

  return StudentCourse;

};

models/index.js (Associations)

const Student = require(“./student”);

const Course = require(“./course”);

const StudentCourse = require(“./studentCourse”);

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

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

module.exports = { Student, Course, StudentCourse };

controllers/courseController.js

const { Course, Student } = require(“../models”);

exports.assignStudentToCourse = async (req, res) => {

  try {

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

    await course.addStudents(req.body.students);

    res.status(201).json({ message: “Students assigned to course.” });

  } catch (error) {

    res.status(500).json({ error: error.message });

  }

};

Payload Example

{

  “course”: { “title”: “Mathematics” },

  “students”: [ { “id”: 1, “grade”: “A” }, { “id”: 2, “grade”: “B” } ]

}


Conclusion

By implementing Sequelize associations correctly, we enable our database models to interact efficiently, mirroring real-world relationships. Using Sequelize’s ORM capabilities, we:

  • Established One-to-One, One-to-Many, and Many-to-Many relationships.
  • Created controllers to manage data creation and retrieval.
  • Utilized structured payloads for inserting and retrieving data.

This approach ensures modularity, maintainability, and scalability in any Node.js application. 🚀

Sequelize Associations with Controllers & Payloads

In this document, we will cover Sequelize associations with real-world scenarios, including:

  • One-to-One
  • One-to-Many
  • Many-to-Many

Each section will include:

  • Model definitions
  • Controller implementations
  • Example payloads
  • Queries to retrieve data

1. Project Structure

project-folder/

│– models/

│   │– student.js

│   │– identityCard.js

│   │– department.js

│   │– course.js

│   │– studentCourse.js

│   │– index.js

│– associations/

│   │– associations.js

│– controllers/

│   │– studentController.js

│   │– departmentController.js

│   │– courseController.js

│– app.js

│– database.js


2. One-to-One Association (Student & IdentityCard)

Definition:

A one-to-one relationship means that one record in a table is associated with exactly one record in another table. In this case, each student has one identity card, and each identity card belongs to one student.

models/student.js

module.exports = (sequelize, DataTypes) => {

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

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

    name: DataTypes.STRING,

  });

  return Student;

};

models/identityCard.js

module.exports = (sequelize, DataTypes) => {

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

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

    cardNumber: DataTypes.STRING,

  });

  return IdentityCard;

};

associations/associations.js

const Student = require(“../models/student”);

const IdentityCard = require(“../models/identityCard”);

const Department = require(“../models/department”);

const Course = require(“../models/course”);

const StudentCourse = require(“../models/studentCourse”);

// One-to-One Association

Student.hasOne(IdentityCard, { foreignKey: “studentId” });

IdentityCard.belongsTo(Student, { foreignKey: “studentId” });

// One-to-Many Association

Department.hasMany(Student, { foreignKey: “departmentId” });

Student.belongsTo(Department, { foreignKey: “departmentId” });

// Many-to-Many Association

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

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

module.exports = { Student, IdentityCard, Department, Course, StudentCourse };

controllers/studentController.js

const { Student, IdentityCard } = require(“../associations/associations”);

exports.createStudentWithCard = async (req, res) => {

  try {

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

    const identityCard = await IdentityCard.create({

      …req.body.identityCard,

      studentId: student.id,

    });

    res.status(201).json({ student, identityCard });

  } catch (error) {

    res.status(500).json({ error: error.message });

  }

};

Payload Example

{

  “student”: { “name”: “John Doe” },

  “identityCard”: { “cardNumber”: “1234567890” }

}


3. One-to-Many Association (Department & Students)

Definition:

A one-to-many relationship means that one record in a table is related to multiple records in another table. Here, one department can have many students, but each student belongs to only one department.

models/department.js

module.exports = (sequelize, DataTypes) => {

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

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

    name: DataTypes.STRING,

  });

  return Department;

};

controllers/departmentController.js

const { Department, Student } = require(“../associations/associations”);

exports.createDepartmentWithStudents = async (req, res) => {

  try {

    const department = await Department.create(req.body.department);

    const students = await Student.bulkCreate(req.body.students.map(stu => ({ …stu, departmentId: department.id })));

    res.status(201).json({ department, students });

  } catch (error) {

    res.status(500).json({ error: error.message });

  }

};

Payload Example

{

  “department”: { “name”: “Computer Science” },

  “students”: [ { “name”: “John” }, { “name”: “Emma” } ]

}


4. Many-to-Many Association (Students & Courses)

Definition:

A many-to-many relationship means that multiple records in one table are associated with multiple records in another table. In this case, students can enroll in multiple courses, and courses can have multiple students.

models/course.js

module.exports = (sequelize, DataTypes) => {

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

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

    title: DataTypes.STRING,

  });

  return Course;

};

models/studentCourse.js (Junction Table)

module.exports = (sequelize, DataTypes) => {

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

    grade: DataTypes.STRING,

  });

  return StudentCourse;

};

controllers/courseController.js

const { Course, Student } = require(“../associations/associations”);

exports.assignStudentToCourse = async (req, res) => {

  try {

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

    await course.addStudents(req.body.students);

    res.status(201).json({ message: “Students assigned to course.” });

  } catch (error) {

    res.status(500).json({ error: error.message });

  }

};

Payload Example

{

  “course”: { “title”: “Mathematics” },

  “students”: [ { “id”: 1, “grade”: “A” }, { “id”: 2, “grade”: “B” } ]

}


Conclusion

By implementing Sequelize associations correctly, we enable our database models to interact efficiently, mirroring real-world relationships. Using Sequelize’s ORM capabilities, we:

  • Established One-to-One, One-to-Many, and Many-to-Many relationships.
  • Created controllers to manage data creation and retrieval.
  • Utilized structured payloads for inserting and retrieving data.

This approach ensures modularity, maintainability, and scalability in any Node.js application. 🚀

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 *