3.3.2: Sequelize Many-To-Many (M-M) Relationships
Learning Objectives
Sequelize provides relationship methods to query for data related through M-M relationships
Understand how to set up models and migrations to support M-M relationships with Sequelize
Understand how to query data related through M-M relationships with Sequelize
Understand how to add and query for data in junction tables for M-M relationships
Introduction
Similar to 1-M relationships, Sequelize provides relationship methods for M-M relationships that make it easy to query related data. Using our Person
and Personality
example from the SQL M-M submodule where each person can have many personalities and vice versa, Sequelize allows us to call methods such as person.getPersonalities()
and personality.getPeople()
to retrieve related data.
The following is Sequelize's official introduction to M-M relationships with Sequelize.
Note the concept of the junction table is central to many to many relationships
In Sequelize's example their junction table is called
actor_movies
, but it could also have been calledmovie_actors
if they wanted. The convention is to name the junction table with both model names concatenated, where the 1st model name is singular and the 2nd is plural. It does not matter which model is 1st and which is 2nd.Note that we declare an M-M relationship between 2 models with the
belongsToMany
association method, where we specify the junction table name in a 2nd parameter with athrough
attributeWe will need to create the junction table
actor_movies
using a migration because we are not using Sequelizesync
There is no need to define the
actor_movies
junction model ourselves unless we plan to include non-foreign-key data in that model. However, we will always need to create theactor_movies
table with a migration because Sequelize will not create it for us.
Models and Migrations for M-M relationships with Sequelize
We will continue with the People
and Personalities
examples from the SQL M-M Relationships submodule.
When to define new model for junction table
If the 2 tables we are associating with an M-M relationship do not need to store non-foreign-key data in their junction table, we do not need to create a standalone model for the junction table in our app. However, we will always need to use a migration to create the junction table in our database.
In this section let us assume there is no non-foreign-key data in the junction table and focus on model association methods and the migration for the junction table.
Model association methods for M-M relationships
Given Person
and Personality
models, we apply the belongsToMany
association method on each of them toward the other to set up an M-M relationship in Sequelize. Like in the Actor
and Movie
example in the Sequelize official docs above, we declare the through table name person_personalities
in the belongsToMany
method. This also means we need to create a person_personalities
table in our database using migrations.
Junction table migration for M-M relationships
Now that we've set up the M-M association between Person
and Personality
models, let's create a migration to create the junction table person_personalities
in our database. The following migration assumes we have past migrations that have already created people
and personalities
tables.
Notice there are 2 foreign keys in the person_personalities
junction table, 1 referencing the people
table and the other referencing the personalities
table. Even though the key in the references
object is model
, Sequelize expects the value to be a plural table name.
Just like that, we've set up an M-M association in our app and are ready to start using Sequelize relationships methods on this relationship!
Query M-M Associated Tables in Sequelize
In the Sequelize 1-M Relationships submodule we read about "special methods/mixins added to instances" and ignored the belongsToMany
section. Let's go back and read the belongsToMany
section to understand what relationship methods Sequelize provides to models associated with belongsToMany
.
Probably the most common relationship method we will use is
fooInstance.getBars()
, simply getting all the instances of the related table associated with the current instance. For example,person.getPersonalities()
.No need to worry about retrieving join table attributes in this section. We will explore that in the "Handling non-foreign-key data in junction table" section below.
Note the large number of relationship methods we can call on model instances with a M-M relationship, including methods to count the number of related model instances, check existence of related model instances, set, add, remove, create related model instances. We will likely not need to use all of these, but good to keep in mind in case using them can simplify our code logic!
We can generally assume that Sequelize will get the plural form of our model names right. For example, we can assume that the Sequelize relationship method to get personalities associated with a
Person
instance will bePerson.getPersonalities()
and not something likePerson.getPersonalitys()
.
Handling non-foreign-key data in junction table
When we wish to store and retrieve non-foreign-key data in our junction table and access that data from our apps, we will need to explicitly create a new model in our apps for the junction model. Once we've created the junction model and junction table migration we can discuss how to query the junction model in our apps.
Models and migrations for junction tables with non-foreign-key data
Models
We must explicitly define the junction table model if we wish to query non-foreign-key data in the junction table. We name the model the singular of the table name, i.e. since the table name was person_personalities
, the model is PersonPersonality
.
In the model is the intensity
attribute that accompanies each person's personality trait. We also include an id
attribute, typically omitted in models that we will include here to enable querying our junction table (more on this below). We also include the foreign keys to People
and Personalities
tables respectively.
Notice there are 2 belongsTo
association methods to Person
and Personality
models respectively. Recall belongsTo
associations are typically used with 1-M relationships. We included these because we want to declare 1-M associations between both Person
and PersonPersonality
and between Personality
and PersonPersonality
. We will explore why we add these 1-M relationships in the next section.
To complete the 1-M relationships between Person
and PersonPersonality
and Personality
and PersonPersonality
, we will need to add a hasMany
association in both Person
and Personality
models. Below is the same code as before except with the new association.
Migrations
Reference the person_personalities
migration above on this page. Add an intensity
column to the person_personalities
table with integer data type.
Querying junction tables with non-foreign-key data
Sequelize has a separate, comprehensive guide to M-M relationships that includes how to manage non-foreign-key data in junction tables.
Defining junction model to access non-foreign-key junction table attributes
The content on calling
belongsToMany
association methods is the same as what we read aboveNote: defining the junction model ourselves allows us to specify non-foreign-key junction model values when calling Sequelize M-M relationship methods such as
fooInstance.addBar()
, or in the example on this page,user.addProfile()
.No need to worry about composite unique key for now, assume
id
is still the primary key for the junction table
Through table versus normal tables and the "Super Many-to-Many association"
Having an
id
primary key onPersonPersonalities
model is necessary for the "Super Many-to-Many relationship" that allows bothPerson
andPersonality
instances to query both the junction table and the table associated via the M-M relationship. I.e., to allowPerson
to query both relatedPersonPersonality
andPersonality
instances, vice versa forPersonality
.Notice that the tables in the database look the same regardless of whether we declare 2 1-M relationships between
person
andperson_personality
andpersonality
andperson_personality
, or 1 M-M relationship betweenperson
andpersonality
.The "Super Many-to-Many relationship" requires us to declare 1-M relationships between the 2 M-M tables and the junction table, and an M-M relationship between the 2 M-M tables. In our
person
andpersonality
example, this means declaring 1-M relationships betweenperson
andperson_personality
and betweenpersonality
andperson_personality
, and an M-M relationship betweenperson
andpersonality
.We can ignore everything below "Aliases and custom key names" in the Sequelize M:N Associations guide for now. Those concepts are more advanced and we can come back to them as reference if we need them.
Querying the junction model
With the "super M-M relationship" we can query our junction model in multiple ways. The cleanest way is to query using M-M relationship methods with the joinTableAttributes
option.
Alternatively we can use 1-M relationship methods to access the junction table, but that may require more acrobatics with Person
and Personality
IDs.
Additional Resources
React Select is a React component library that provides an elegant UI for collecting 1-M and M-M relationship data.