Understanding Sequelize Associations: Part 3: Many to Many (n:m) mapping

Siddharth Lakhara
6 min readJan 2, 2020

--

This is the last part of 3 part blog series in which I explain sequelize associations by creating a CRUD app. The description and links of each part is as follows:

This blog assumes that you have basic understanding of NodeJs, Databases and Sequelize.

Source repo for this blog

Complete repo link

1. What are we going to do in this blog

In this blog, I will explain n:m mapping in sequelize by creating a CRUD app. We will be working on the same app we used in the last blog. The tech stack of the app is as follows:

Till the last blog, we have created tables: users, userDetails and posts. In this part, we will create an additional table called groups. This table will have n:m (many to many) relation with our users table. We will associate these two tables through users_groups table. Our through table will contain the mapping of userId and groupId, which belongs to the users and groups table respectively. The updated ER diagram is as follows:

Updated ER diagram

2. Generate Models

We have already created users, usersDetails and posts in the last post. Now, we will create groups model. We will also have to create a through table named users_groups. Similar to the last time, we will first create our models using sequelize-cli, then modify the generated models according to our associations

2.1 Create models using sequelize-cli

Run the following command to create our new model groups. This command will also generate relevant migrations and models.

node_modules/.bin/sequelize model:generate --name groups --attributes groupName:string

The following will generate users_groups migration and relevant model:

node_modules/.bin/sequelize model:generate --name users_groups --attributes userId:integer,groupId:integer

2.2 Modify models for association

Now that we have our migrations and models generated by sequelize, we will modify them according to our associations. Find the migration titled like: create-users-groups and modify the userId, and groupId attribute to match as follows:

userId: {
type: Sequelize.INTEGER,
references: { model: 'users', key: 'id' },
onDelete: 'CASCADE',
},
groupId: {
type: Sequelize.INTEGER,
references: { model: 'groups', key: 'id' },
onDelete: 'CASCADE',
}

Now, we will start modifying models. Find the model groups and add associations as follows:

groups.associate = (models) => {
groups.belongsToMany(models.users, {
foreignKey: 'groupId',
through: 'users_groups',
as: 'users'
});
};

Next, we will modify users model. We already have some associations defined on this model. We will add one more association in it. The final associations should look as follows:

users.associate = function (models) {
users.hasOne(models.userDetails, {
foreignKey: 'userId',
as: 'userDetails',
onDelete: 'CASCADE'
});
users.hasMany(models.posts, {
foreignKey: 'userId',
as: 'posts'
});
// we are adding the following
users.belongsToMany(models.groups, {
foreignKey: 'userId',
through: 'users_groups',
as: 'groups'
});
};

Notice that the above two models have belongsToMany associations, and they have through table defined. This is to tell sequelize the model that it should map while creating our many-to-many relation. Another crucial thing to notice here is the value of foreignKey field. Until now, we have been using the field of the source model in the definition. But things are a little different with belongsToMany keyword. Now, we are using the foreign key in our target model i.e. our through table. This stack overflow answer summarises the usage quite well

Next, we will modify users_groups model, which is our through table. Add the following associations:

users_groups.associate = (models) => {
users_groups.belongsTo(models.users, {
foreignKey: 'userId',
as: 'user'
});
users_groups.belongsTo(models.groups, {
foreignKey: 'groupId',
as: 'group'
});
};

2.3 Side note: what are hasOne, belongsTo, hasMany, belongsToMany ?

I explained this in previous parts too, but in order to keep the blog complete, I’m again explaining them

All the four keywords are used to define associations in our sequelize model. One way to differentiate them is to consider the following points:

1. Whenever the foreign key is defined on the source model (the model for which we are writing this association), use belongsTo or belongsToMany

2. If the foreign key is defined on the target model (the model with which we will be associating), use hasOne or hasMany

3. If we are considering multiple mappings (1:n, or n:m), then, we have to use hasMany or belongsToMany

For eg, in this scenario, userDetails has the foreign key userId. Hence, I have used hasOne constraint. Read like: model users has one key defined on the model userDetails with name key as userId.

Similarly, if we had defined a belongsTo relation on userDetails, this would have been as follows:

userDetails.associate = function (models) {
userDetails.belongsTo(models.users, {
foreignKey: 'userId',
as: 'users'
});
};

This can be read like: model userDetails has a foreign key which belongs to the model users. The foreign key is userId.

2.4 Run migrations

Now that we have all our models and migrations ready. We will run the migrations, so that our corresponding tables are replicated in our database. Run the following command:

node_modules/.bin/sequelize db:migrate

Sequelize will run new migrations only. We will now be able to see foreign key constraints defined on our tables:

New constraints on users table
Constraints on users_groups table
Constraints on groups table

3. Creating CRUD app

We will now move on by modifying our CRUD app that we built in the last post. We will add new API for working with groups. Also, we will have to modify our existing users API, to reflect new changes. The complete file can be found here: Users API, Groups API

3.1 Create API

We need to have two create API. One to create a group, another to add user to a particular group. The code for create group API looks as follows:

Create groups

The code for adding user to a group:

Add users to group

We also need to modify our users API. We now need to pass groups details along with other details. A sample payload for this API will now be as follows:

{
"name": "ABCD",
"userName": "abcd.abcd",
"mobileNum": "1234567890",
"address": "Earth",
"posts": [{
"title": "some post title 1"
}, {
"title": "some post title 2"
}, {
"title": "some post title 3"
}, {
"title": "some post title 4"
}],
"groups": {
"groupName": "group with users"
}
}

The corresponding changes in our create users API is as follows:

Git diff of create users API

In case you want to see the full file, find it here

3.2 Read API

We will have two read API for groups. One will fetch us all the groups. Another will fetch us a particular group with all its users.

Following is code for first API which will fetch all groups:

Fetch all groups

Following is the code for second API, which fetches users of a particular group:

Fetch users of a group

3.3 Update API

API for updating posts can be written as shown below:

Update group details API

3.4 Delete API

Similar to read API, we will have two delete API. One will delete a particular group. Another will delete a user from a group

The code for these API is as follows:

Delete group APIs

Wrapping Up

In this blog, I showed how you can create n:m mapping and utilise sequelize functions to do basic tasks. This blog marks the end of my series of sequelize associations. I hope you liked it. If you have any questions, please comment them. I will try to answer them as soon as possible

Source repo for this blog: link

Complete repo:

If you like this post, clap this post to show your appreciation. More number of claps motivates me to keep writing such posts.

Follow me on Github and Medium

--

--

Siddharth Lakhara

Full stack developer @Cisco, Ex-@McKinsey | Bibliophile | Coder by heart | Opinions are mine