3.3.1: Sequelize One-To-Many (1-M) Relationships
Learning Objectives
Sequelize provides "special" relationship methods (aka mixins) attached to models to query data with 1-1, 1-M and M-M relationships
Understand steps to set up Sequelize's relationship methods
Understand how to use Sequelize's relationship methods
Understand how to use migrations to add foreign keys needed for Sequelize relationships
Introduction
Sequelize makes it easy to query for data with "special" relationship methods (aka mixins) built into Sequelize. To set up these methods we will need to tell Sequelize which models are associated with each other and how they are associated, e.g. users
and posts
are related via a 1-M relationship. We will also need to ensure our database tables have relevant foreign key columns using migrations if necessary. We will explore what Sequelize relationship methods look like and how to set them up in this submodule.
Recall our Users
and Posts
example from the SQL 1-M Relationships submodule where users have many posts, and each post belongs to a single user. In that module we demonstrated how to query posts for a given user using SQL, like the following.
The equivalent query in Sequelize would look like the following. Note that all Sequelize query methods return JavaScript promises because the methods query a remote database. We can choose to handle these promises with await
or .then
syntax.
We will use Sequelize instead of raw SQL in our apps because writing queries in JavaScript instead of SQL strings allows VS Code to more easily catch syntax errors for us. This makes our apps more robust and less verbose.
Sequelize Associations
Let's go through Sequelize's official introduction to associations section by section.
Defining the Sequelize associations
Notice the 4 types of associations Sequelize uses to specify relationships between models:
HasOne
,BelongsTo
,HasMany
andBelongsToMany
. We will useBelongsTo
andHasMany
for 1-M relationships.HasOne
andBelongsTo
are used for 1-1 relationships andBelongsToMany
is used for M-M relationships.To tell Sequelize that model
A
has a 1-M relationship with modelB
andA
is the "1" andB
is the "M" in the relationship, we would callA.hasMany(B);
andB.belongsTo(A);
. Both calls are needed to include Sequelize relationship methods on bothA
andB
. SinceB
is the "M" in the relationship, Sequelize will assume there is a foreign keyAId
(by default the related model's name (i.e.A
) followed byId
) that referencesA
's primary keyid
inB
's model and underlying SQL table.No need to worry about the options 2nd parameter,
hasOne
andbelongsToMany
methods for nowNote in which table Sequelize expects the foreign key to be for each association. When
A.hasMany(B)
, foreign key is in the target modelB
. WhenB.belongsTo(A)
, foreign key is in the source modelB
.
Creating the standard relationships
We will focus on One-To-Many relationships in this submodule with
hasMany
andbelongsTo
associations
One-To-Many relationships (1-M)
Note the explanation that there is only 1 option for which table contains the foreign key in a 1-M relationship: the "M" table.
In our apps, we will declare Sequelize associations like
Team.hasMany(Player);
andPlayer.belongsTo(Team);
in class definitions indb/models/team.js
anddb/models/player.js
, the files in which we define our models. We will declare the associations from within each class using thethis
keyword, likethis.hasMany(models.Player)
from theTeam
class andthis.belongsTo(models.Team)
from thePlayer
class. More examples in "Update models and migrations" section below.We will not use
sync
as explained in Rocket's previous Sequelize submodule because it can cause unintended behaviour in production.No need to worry about the Options section for now, we will stick to the defaults first
Basics of queries involving associations
Sequelize uses a 1-1 relationship between
Ship
andCaptain
models here but the concepts are the same for 1-M relationshipsLazy loading is fetching data without using joins. Eager loading is fetching data with joins using Sequelize syntax.
getShip()
is one of the "special" relationship methods we mentioned at the top of this page that allow us to query related data using Sequelize. In this case, becauseShip
andCaptain
are related with a 1-1 relationship, we can callgetShip()
on an instance ofCaptain
to get that captain's ship. More on these methods in the Special methods/mixins section below.Eager loading can be helpful to retrieve associated data in a single database query. We will use this in our Bigfoot SQL M-M exercise.
Ignore the
save()
method, we will not use it because we will use the built-increate
,update
anddestroy
methods that performsave
automatically.
Special methods/mixins added to instances
This section documents all "special" relationship methods we referred to at the top of this page. Note that these relationship methods will only be available on model instances when we associate relevant models with the relevant association methods, e.g.
belongsTo
andhasMany
. For example, if I declaredA.hasMany(B)
andB.belongsTo(A)
, I could calla.getBs()
(wherea
is an instance of modelA
) but notb.getAs()
(whereb
is an instance of modelB
) because eachB
belongs to only 1A
, not multiple.All Sequelize relationship methods return JavaScript promises because they query a remote database
In case you're wondering, "foo" and "bar" (or just "foobar") are common placeholder names in computer science
Note Sequelize performs pluralisation automatically and intelligently such that we can assume the relationship method names use accurate English, e.g.
getPerson
for a 1-1 relationship andgetPeople
for a 1-M relationship with aPerson
model.We can ignore the special methods for
belongsToMany
associations for now. We will revisit them in the Sequelize M-M submodule.Great work! This is the most important section of this submodule, and we will be using these "special" relationship methods often!
Why associations are defined in pairs?
Always define relationships in pairs in Sequelize, e.g.
hasMany
andbelongsTo
for 1-M relationships! This will ensure we have the relevant built-in relationship methods when we need them.Ignore the sections below this one in the Sequelize Associations page. We will not use them for now, if at all at Rocket. We will solidify our fundamentals first before learning advanced concepts.
Update models and migrations to add foreign keys
Introduction
The above documentation showed us how to declare Sequelize associations and use Sequelize relationship methods, but touched little on how to add foreign keys in model definitions and in our database schema using migrations. For Sequelize associations to work we will also need to update model and migration files such that our models and our database have the relevant foreign keys.
Let's again use our hypothetical Users
and Posts
social media example where each user has many posts. Because there is a 1-M relationship between Users
and Posts
respectively, there needs to be a foreign key UserId
on the Posts
table. Let's see how our models and migrations might look with this foreign key.
Models
Most of db/models/user.js
where we define our User
model is boilerplate except for this.hasMany(models.post)
where we declare User
's association with the Post
model
Like User
, we also declare an association in Post
, this time belongsTo
instead of hasMany
. Unlike User
, Post
needs a foreign key to complete their 1-M relationship, and we will give this foreign key a non-default name for clarity. We define this foreign key as AuthorId
instead of UserId
, because UserId
is less precise and could cause confusion as more users are associated with posts, e.g. users that like or comment on posts.
There are 2 points to note:
In our association method
belongsTo
we add a 2nd options parameter{ as: "Author" })
that instructs Sequelize to use theAuthor
alias forUser
s in this relationship. This has 2 consequences:All Sequelize relationship methods that would otherwise have looked like
post.getUser()
will now look likepost.getAuthor()
instead (official explanation)Sequelize now expects there to be a foreign key
AuthorId
on thePost
model andPosts
table instead ofUserId
We define the foreign key
AuthorId
as a property of thePost
model. In addition to specifying the property's data type, we also specify the model and property this foreign key references, in this case theid
property of theUser
model.
The above changes will allow us to use Sequelize relationships in our apps, assuming our underlying SQL database also has the relevant foreign keys. We will need to add new database migrations to add relevant foreign keys to our database schema.
Migrations
The following database migrations assume we do not have existing tables in our database. If we do have existing tables in our database whose schemas need to be edited, we will either need to:
Edit existing migrations, drop existing database (
dropdb
is a convenient CLI method), create new database (createdb
is a convenient CLI method) and re-run migrations (npx sequelize db:migrate
)Create new migrations to edit tables in existing database (
npx sequelize migration:generate
)
Rocket recommends option #1 for apps with no live user data to maximise development speed. Apps with live user data only have option #2, since we should never drop a database with live user data.
The migration to create the users
table has no foreign key, since there is no foreign key on the User
model.
The migration to create the posts
table contains the author_id
foreign key, declared almost identically as it was in db/models/post.js
above. Sequelize expects the value for the models
key in the references
object to reference a plural table name. Documentation for declaring foreign keys in migrations here.
Once we have updated our models to include associations and foreign keys, updated our migrations to include foreign keys and run those migrations on our database, we are ready to start using Sequelize relationship methods in our apps!
New to Rocket Academy?
If you're not enrolled in Rocket's Bootcamp and visiting this page, check out our website to learn more about our Bootcamp course!