The Three Types of Relationships
Imagine you're modeling a digital library system. Here's how different relationships might work:
One-to-One (1:1)
Like a library card belonging to exactly one person, and a person having exactly one library card.
// Library Card Model
module.exports = (sequelize, DataTypes) => {
const LibraryCard = sequelize.define('LibraryCard', {
cardNumber: {
type: DataTypes.STRING,
unique: true
},
issuedDate: DataTypes.DATE
});
LibraryCard.associate = (models) => {
// One card belongs to one user
LibraryCard.belongsTo(models.User);
};
return LibraryCard;
};
// User Model
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: DataTypes.STRING,
email: DataTypes.STRING
});
User.associate = (models) => {
// One user has one card
User.hasOne(models.LibraryCard);
};
return User;
};
One-to-Many (1:N)
Like an author writing many books, but each book having only one author.
// Author Model
module.exports = (sequelize, DataTypes) => {
const Author = sequelize.define('Author', {
name: DataTypes.STRING,
biography: DataTypes.TEXT
});
Author.associate = (models) => {
// One author has many books
Author.hasMany(models.Book, {
foreignKey: 'authorId',
as: 'books'
});
};
return Author;
};
// Book Model
module.exports = (sequelize, DataTypes) => {
const Book = sequelize.define('Book', {
title: DataTypes.STRING,
publishYear: DataTypes.INTEGER
});
Book.associate = (models) => {
// Each book belongs to one author
Book.belongsTo(models.Author, {
foreignKey: 'authorId',
as: 'author'
});
};
return Book;
};
Many-to-Many (N:M)
Like students taking multiple courses, and courses having multiple students.
// Student Model
module.exports = (sequelize, DataTypes) => {
const Student = sequelize.define('Student', {
name: DataTypes.STRING,
grade: DataTypes.INTEGER
});
Student.associate = (models) => {
// Students can have many courses
Student.belongsToMany(models.Course, {
through: 'StudentCourses',
as: 'courses',
foreignKey: 'studentId'
});
};
return Student;
};
// Course Model
module.exports = (sequelize, DataTypes) => {
const Course = sequelize.define('Course', {
title: DataTypes.STRING,
description: DataTypes.TEXT
});
Course.associate = (models) => {
// Courses can have many students
Course.belongsToMany(models.Student, {
through: 'StudentCourses',
as: 'students',
foreignKey: 'courseId'
});
};
return Course;
};
Real-World Example: Social Media Platform
// User Model with multiple relationships
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
username: {
type: DataTypes.STRING,
unique: true
},
email: DataTypes.STRING
});
User.associate = (models) => {
// One-to-One: User has one profile
User.hasOne(models.Profile, {
as: 'profile',
onDelete: 'CASCADE'
});
// One-to-Many: User has many posts
User.hasMany(models.Post, {
as: 'posts',
foreignKey: 'authorId'
});
// Many-to-Many: Users can follow many users
User.belongsToMany(models.User, {
through: 'Followers',
as: 'followers',
foreignKey: 'followingId',
otherKey: 'followerId'
});
User.belongsToMany(models.User, {
through: 'Followers',
as: 'following',
foreignKey: 'followerId',
otherKey: 'followingId'
});
};
return User;
};
Working with Associated Data
// Example queries with associations
const getUserWithProfile = async (userId) => {
return await User.findByPk(userId, {
include: [{
model: Profile,
as: 'profile'
}]
});
};
const getAuthorWithBooks = async (authorId) => {
return await Author.findByPk(authorId, {
include: [{
model: Book,
as: 'books'
}]
});
};
const getStudentWithCourses = async (studentId) => {
return await Student.findByPk(studentId, {
include: [{
model: Course,
as: 'courses',
through: { attributes: [] } // Exclude junction table
}]
});
};
Association Options and Customization
// Example with custom options
module.exports = (sequelize, DataTypes) => {
const Team = sequelize.define('Team', {
name: DataTypes.STRING
});
Team.associate = (models) => {
// Custom association with constraints
Team.belongsToMany(models.User, {
through: 'TeamMembers',
as: 'members',
foreignKey: 'teamId',
otherKey: 'userId',
constraints: true,
onDelete: 'CASCADE',
scope: {
active: true
}
});
// Association with conditions
Team.hasMany(models.Project, {
foreignKey: 'teamId',
as: 'activeProjects',
scope: {
status: 'active'
}
});
};
return Team;
};
Best Practices
- Use Meaningful Aliases: Give your associations clear, descriptive names
- Consider Cascade Effects: Define appropriate onDelete and onUpdate behaviors
- Plan Relationships Carefully: Design your data model before implementing
- Use Constraints: Implement proper database constraints
- Document Associations: Maintain clear documentation of relationships
Common Pitfalls to Avoid
- Circular dependencies in associations
- Missing foreign key definitions
- Incorrect relationship types
- Inadequate error handling
- Performance issues with deep includes
Advanced Topics to Explore
- Polymorphic associations
- Scopes and default scopes
- Eager loading strategies
- Association mixins
- Complex queries with associations