Welcome to this detailed tutorial on defining associations in Sequelize! Imagine you are building a network of roads connecting different cities. Each city represents a table in your database, and the roads represent the relationships between these tables. In Sequelize, these relationships are expressed through associations that connect your models in a clear and organized manner.
In this lesson, you will learn how to:
associate static method on a model to define relationships
In Sequelize, each model can include a static associate method. Think of this method as the blueprint for connecting your city (model)
to other cities. When your models are initialized, Sequelize calls this method to set up the relationships between your tables.
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class User extends Model {
static associate(models) {
// Define associations here
}
}
User.init({
firstName: DataTypes.STRING,
lastName: DataTypes.STRING
}, {
sequelize,
modelName: 'User'
});
return User;
};
In this example, the associate method is defined within the User model. This method will be used to connect the User model
with other models, such as Preferences, Posts, or any other related data.
Associations in Sequelize are used to define how different models (tables) relate to each other. These associations are one-way connections that, when combined, represent the full two-way relationship between tables.
For example, consider the common relationships:
hasOne another model,
and the other model belongsTo the first.
hasMany students,
and each student belongsTo a teacher.
Let’s start with a one-to-one example. Suppose a User has one set of Preferences. In the User model, you can define:
class User extends Model {
static associate(models) {
User.hasOne(models.Preference, { foreignKey: 'userId' });
}
}
module.exports = (sequelize, DataTypes) => {
class Preference extends Model {
static associate(models) {
Preference.belongsTo(models.User, { foreignKey: 'userId' });
}
}
Preference.init({
theme: DataTypes.STRING,
notifications: DataTypes.BOOLEAN
}, {
sequelize,
modelName: 'Preference'
});
return Preference;
};
Here, the User model "has one" Preference, and the Preference model "belongs to" a User.
This setup ensures that each user’s preferences are uniquely tied to that user.
Now, consider a one-to-many relationship where a Person can have many Jobs. In this case, the Person model will have a hasMany association,
while the Job model will have a belongsTo association:
class Person extends Model {
static associate(models) {
Person.hasMany(models.Job, { foreignKey: 'personId', onDelete: 'CASCADE', hooks: true });
}
}
class Job extends Model {
static associate(models) {
Job.belongsTo(models.Person, { foreignKey: 'personId' });
}
}
module.exports = { Person, Job };
With this configuration, if a Person is deleted, all associated Job records can be automatically removed via cascade deletion.
Finally, let’s explore a many-to-many relationship using a join table. Consider a scenario with Readers and Books. Each Reader can read many Books,
and each Book can be read by many Readers. You define the relationship using belongsToMany on both models:
class Book extends Model {
static associate(models) {
Book.belongsToMany(models.Reader, {
through: models.BookReader,
foreignKey: 'bookId',
otherKey: 'readerId'
});
}
}
class Reader extends Model {
static associate(models) {
Reader.belongsToMany(models.Book, {
through: models.BookReader,
foreignKey: 'readerId',
otherKey: 'bookId'
});
}
}
module.exports = { Book, Reader };
In this case, the join table BookReader is used to store the relationships between Books and Readers.
By defining both sides of the association, you can easily query for all books read by a specific reader, or all readers of a particular book.
In complex applications, a single model might have multiple associations. For instance, a User might have a one-to-one relationship with Preferences,
a one-to-many relationship with Posts, and a many-to-many relationship with Groups. Simply define each association within the associate method:
class User extends Model {
static associate(models) {
User.hasOne(models.Preference, { foreignKey: 'userId' });
User.hasMany(models.Post, { foreignKey: 'userId' });
User.belongsToMany(models.Group, { through: models.UserGroup, foreignKey: 'userId', otherKey: 'groupId' });
}
}
This setup allows you to retrieve a user's preferences, posts, and groups all through their User model, making your code more cohesive and easier to work with.
It is important to understand that defining an association in your models prepares Sequelize to work with related data—it doesn’t generate SQL commands immediately. Once associations are defined, they are used when you perform queries. For example, when you query a User and include their Posts, Sequelize uses the associations to generate the appropriate JOIN queries.
In essence, defining associations is like laying out the tracks in your railway system. The tracks (associations) need to be in place so that when you start your train (querying data), it knows exactly which connections to take.
Let’s put theory into practice. Imagine you are building a blog application with Users and Posts.
First, open your User model file and add an association:
module.exports = (sequelize, DataTypes) => {
class User extends Model {
static associate(models) {
User.hasMany(models.Post, { foreignKey: 'userId' });
}
}
User.init({
username: DataTypes.STRING,
email: DataTypes.STRING
}, {
sequelize,
modelName: 'User'
});
return User;
};
Next, open your Post model file and add its association:
module.exports = (sequelize, DataTypes) => {
class Post extends Model {
static associate(models) {
Post.belongsTo(models.User, { foreignKey: 'userId' });
}
}
Post.init({
title: DataTypes.STRING,
content: DataTypes.TEXT
}, {
sequelize,
modelName: 'Post'
});
return Post;
};
Now, run your migrations to ensure the tables are created with the appropriate foreign key columns. Finally, test the association with an eager loading query:
const userWithPosts = await User.findOne({
where: { username: 'exampleUser' },
include: [{ model: Post }]
});
console.log(userWithPosts);
This query should return a user and an array of associated posts, demonstrating that your associations are correctly defined.
As you continue to work with Sequelize associations, consider exploring advanced topics such as:
In this lesson, you learned how to define associations in Sequelize using the static associate method and the four core association functions:
hasOne, belongsTo, hasMany, and belongsToMany. You discovered how these associations allow you to model
one-to-one, one-to-many, and many-to-many relationships, effectively connecting your data in an object-oriented way.
Remember, defining associations is like setting up the infrastructure of a railway system – it prepares the connections between different tables. Once the associations are defined, they come into play when you query or manipulate data, enabling seamless interaction with related records.
With this foundation, you are now ready to implement and use associations in your Sequelize models to build powerful, relational applications. Happy coding and enjoy connecting your data!