How to implement many to many association in sequelize
Sequelize Association Cheatsheet
Updated for Sequelize v2/3/4/5
Generally I think the problems are that we are confused about what tables created, and what methods are gained by associations.
Note: Defining foreignKey or cross table name are optional. Sequelize automatically creates it, but defining it allows coders to read the models and find out what the foreign keys/cross table names are, instead of guessing or needing to access the database.
TLDR;
O:O
// foreign key has to be defined on both sides.Parent.hasOne(Child, {foreignKey: 'Parent_parentId'})// "Parent_parentId" column will exist in the "belongsTo" table.Child.belongsTo(Parent, {foreignKey: 'Parent_parentId'})
O:M
Parent.hasMany(Child, {foreignKey: 'Parent_parentId'})Child.belongsTo(Parent, {foreignKey: 'Parent_parentId'})
N:M
Parent.belongsToMany( Child, { // this can be string (model name) or a Sequelize Model Object Class // through is compulsory since v2 through: 'Parent_Child', // GOTCHA // note that this is the Parent's Id, not Child. foreignKey: 'Parent_parentId' })/*The above reads:"Parents" belongs to many "Children", and is recorded in the "Parent_child" table, using "Parents"'s ID.*/Child.belongsToMany( Parent, { through: 'Parent_Child', // GOTCHA // note that this is the Child's Id, not Parent. foreignKey: 'Child_childId' })
Why the verbose "Parent_parentId" and not just "parentId"? This is to make it obvious that it's a foreign key that belonged to "Parent". In most cases it's okay to just use the more succinct "parentId".*
Associations gives you 2 functionality: (1) Eager loading and (2) DAO Methods:
1. Include (Eager loading)
DB.Parent.findOne({ where: { id: 1 }, include: [ DB.Child ]}).then(parent => { // you should get `parent.Child` as an array of children. })
2. Methods gained by hasOne(), hasMany() and belongsTo()/belongsToMany()
Associations give the Data Access Object (DAO) methods:
hasOne():In setting a Parent.hasOne(Child)
, methods available to parent
DAO instance:
DB.Parent.findOne({ where: { id: 1 } }).then(parent => { // `parent` is the DAO // you can use any of the methods below: parent.getChild parent.setChild parent.addChild parent.createChild parent.removeChild parent.hasChild})
hasMany():In setting a Parent.hasMany(Child)
, methods available to parent
DAO instance:
parent.getChildren,parent.setChildren,parent.addChild,parent.addChildren,parent.createChild,parent.removeChild,parent.hasChild,parent.hasChildren,
belongsTo()/belongsToMany:In setting a Child.belongsTo(Parent)
, methods available to child
DAO instance:
child.getParent,child.setParent,child.createParent,//belongsToManychild.getParents,child.setParents,child.createParents,
You can also have multiple relationships
Natural Parents/Children// a parent can have many childrenParent.belongsToMany(Child, { as: 'Natural', through: 'Parent_Child', foreignKey: 'Parent_parentId'})// a child must at least have 2 parents (natural mother and father)Child.belongsToMany(Parent, { as: 'Natural', through: 'Parent_Child', foreignKey: 'Child_childId'})
Foster Parents/ChildrenParent.belongsToMany(Child, { as: 'Foster', through: 'Parent_Child', foreignKey: 'Parent_parentId'})Child.belongsToMany(Parent, { as: 'Foster', through: 'Parent_Child', foreignKey: 'Child_childId'});
The above will create the Parent_Child
cross table, with NaturalId
and FosterId
.
delete BookArticles model and update relation to:
m.Book.hasMany(m.Article, {through: 'book_articles'});m.Article.hasMany(m.Books, {through: 'book_articles'});
This is how i solved the similar problem i had two models a user model
var user = sequelize.define('user', { name: { Sequelize.STRING(255) }, email: { type: Sequelize.STRING(255), unique: true, validate: { isEmail: true } }});
and a roles model
var Role = sequelize.define('role', { name: { Sequelize.ENUM('ER', 'ALL', 'DL') }, description: { type: Sequelize.TEXT }});
Then i created the union model UserRole
var UserRole = sequelize.define('user_role', { id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, name: { type: Sequelize.ENUM('Admin', 'Staff', 'Customer', 'Owner') }});
Note: you have to explicitly define the id for UserRole otherwise sequelize will use the two foreign keys in this case user_id
and role_id
as your primary keys.
Then i created the belongs to many relationship as follows
User.belongsToMany(Role, { as: 'Roles', through: { model: UserRole, unique: false }, foreignKey: 'user_id' });Role.belongsToMany(User, { as: 'Users', through: { model: UserRole, unique: false }, foreignKey: 'role_id' });