Question

I have two tables: Books and Articles with a many-to-many relationship between them. Joining table is BookArticles.

models/books.js

module.exports = function(sequelize, DataTypes) {
  return Food = sequelize.define("Book", {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      allowNull: false,
      autoIncrement: true,
      unique: true
    }
  });
}

models/articles.js

module.exports = function(sequelize, DataTypes) {
  return Food = sequelize.define("Article", {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      allowNull: false,
      autoIncrement: true,
      unique: true
    }
  });
}

models/bookArticles.js

module.exports = function(sequelize, DataTypes) {
  return Food = sequelize.define("BookArticles", {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      allowNull: false,
      autoIncrement: true,
      unique: true
    },
   bookId: {
      type: DataTypes.INTEGER,
      references: 'Book',
      referencesKey: 'id',
      allowNull: false
    },
    ArticleId: {
      type: DataTypes.INTEGER,
      references: 'Article',
      referencesKey: 'id',
      allowNull: false
    },
  });
}

And models/index.js

m.BookArticles.belongsTo(m.Book);
m.Book.hasMany(m.Article, {through: m.BookArticles});


m.BookArticles.belongsTo(m.Article);
m.Article.hasMany(m.Books, {through: m.BookArticles});

but I could not get book articles

How can I get it ??

Was it helpful?

Solution

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,

//belongsToMany
child.getParents,
child.setParents,
child.createParents,

You can also have multiple relationships

Natural Parents/Children
// a parent can have many children
Parent.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/Children
Parent.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.

OTHER TIPS

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' });

Runnable example with assertions

Here I provide a single-source-file runnable example that illustrates every single autogenreated method mentioned at https://sequelize.org/master/manual/assocs.html#many-to-many-relationships (archive) section "Foo.hasMany(Bar)".

The model is of a website where users can create posts and like other users' posts.

npm install sequelize@6.5.1 sqlite3@5.0.2

main.js

const assert = require('assert');
const path = require('path');

const { Sequelize, DataTypes } = require('sequelize');

const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: 'db.sqlite3',
});

(async () => {

// Create the tables.
const User = sequelize.define('User', {
  name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
  body: { type: DataTypes.STRING },
}, {});
User.belongsToMany(Post, {through: 'UserLikesPost'});
Post.belongsToMany(User, {through: 'UserLikesPost'});
await sequelize.sync({force: true});

// Create some users and posts.

const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})

const post0 = await Post.create({body: 'post0'});
const post1 = await Post.create({body: 'post1'});
const post2 = await Post.create({body: 'post2'});

// Autogenerated add* methods

// Make user0 like post0
await user0.addPost(post0)
// Also works.
//await user0.addPost(post0.id)
// Make user0 and user2 like post1
await post1.addUsers([user0, user2])

// Autogenerated get* methods

// Get posts liked by a user.

const user0Likes = await user0.getPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);

const user1Likes = await user1.getPosts({order: [['body', 'ASC']]})
assert(user1Likes.length === 0);

const user2Likes = await user2.getPosts({order: [['body', 'ASC']]})
assert(user2Likes[0].body === 'post1');
assert(user2Likes.length === 1);

// Get users that like a given post.

const post0Likers = await post0.getUsers({order: [['name', 'ASC']]})
assert(post0Likers[0].name === 'user0');
assert(post0Likers.length === 1);

const post1Likers = await post1.getUsers({order: [['name', 'ASC']]})
assert(post1Likers[0].name === 'user0');
assert(post1Likers[1].name === 'user2');
assert(post1Likers.length === 2);

const post2Likers = await post2.getUsers({order: [['name', 'ASC']]})
assert(post2Likers.length === 0);

// Same as getPosts but with the user ID instead of the model object.
{
  const user0Likes = await Post.findAll({
    include: [{
      model: User,
      where: {
        id: user0.id
      }
    }],
  })
  assert(user0Likes[0].body === 'post0');
  assert(user0Likes[1].body === 'post1');
  assert(user0Likes.length === 2);
}

// Yet another way that can be more useful in nested includes.
{
  const user0Likes = (await User.findOne({
    where: {id: user0.id},
    include: [{
      model: Post,
    }],
    order: [[Post, 'body', 'ASC']],
  })).Posts
  assert(user0Likes[0].body === 'post0');
  assert(user0Likes[1].body === 'post1');
  assert(user0Likes.length === 2);
}

// Autogenerated has* methods

// Check if user likes post.
assert( await user0.hasPost(post0))
assert( await user0.hasPost(post0.id)) // same
assert( await user0.hasPost(post1))
assert(!await user0.hasPost(post2))

// Check if post is liked by user.
assert( await post0.hasUser(user0))
assert(!await post0.hasUser(user1))
assert(!await post0.hasUser(user2))

// AND of multiple has checks at once.
assert( await user0.hasPosts([post0, post1]))
// false because user0 does not like post2
assert(!await user0.hasPosts([post0, post1, post2]))

// Autogenerated count* methods
// user0 likes 2 posts.
assert(await user0.countPosts() === 2)
// post0 is liked by 1 user.
assert(await post0.countUsers() === 1)

// Autogenerated remove* method

// user0 doesn't like post0 anymore.
await user0.removePost(post0)
// user0 and user 2 don't like post1 anymore.
await post1.removeUsers([user0, user2])
// Check that no-one likes anything anymore.
assert(await user0.countPosts() === 0)
assert(await post0.countUsers() === 0)

// Autogenerated create* method
// Create a new post and automatically make user0 like it.
const post3 = await user0.createPost({'body': 'post3'})
assert(await user0.hasPost(post3))
assert(await post3.hasUser(user0))

// Autogenerated set* method
// Make user0 like exactly these posts. Unlike anything else.
await user0.setPosts([post1, post2])
assert(!await user0.hasPost(post0))
assert( await user0.hasPost(post1))
assert( await user0.hasPost(post2))
assert(!await user0.hasPost(post3))

await sequelize.close();
})();

GitHub upstream.

The SQLite table generated is:

UserLikesPost is the name of the relation table.
Sequelize creates it automatically for us.
On SQLite that table looks like this:
CREATE TABLE `UserLikesPost` (
  `createdAt` DATETIME NOT NULL,
  `updatedAt` DATETIME NOT NULL,
  `UserId` INTEGER NOT NULL REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  `PostId` INTEGER NOT NULL REFERENCES `Posts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  PRIMARY KEY (`UserId`, `PostId`)
);

Self-associations (AKA self-references)

This illustrates how to have many-to-many from a table to itself, e.g. for a user to follow another user.

Basically:

  • you are forced to add the as: key to .belongsToMany
  • TODO: no addFollows method was generated, only addFollow, why?
const assert = require('assert');
const path = require('path');

const { Sequelize, DataTypes } = require('sequelize');

const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: 'db.sqlite3',
});

(async () => {

// Create the tables.
const User = sequelize.define('User', {
  name: { type: DataTypes.STRING },
}, {});
User.belongsToMany(User, {through: 'UserFollowUser', as: 'Follows'});
await sequelize.sync({force: true});

// Create some users.

const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})
const user3 = await User.create({name: 'user3'})

// Make user0 follow user1 and user2
await user0.addFollows([user1, user2])
// Make user2 and user3 follow user0
await user2.addFollow(user0)
await user3.addFollow(user0)

// Check that the follows worked.
const user0Follows = await user0.getFollows({order: [['name', 'ASC']]})
assert(user0Follows[0].name === 'user1');
assert(user0Follows[1].name === 'user2');
assert(user0Follows.length === 2);

const user1Follows = await user1.getFollows({order: [['name', 'ASC']]})
assert(user1Follows.length === 0);

const user2Follows = await user2.getFollows({order: [['name', 'ASC']]})
assert(user2Follows[0].name === 'user0');
assert(user2Follows.length === 1);

const user3Follows = await user3.getFollows({order: [['name', 'ASC']]})
assert(user3Follows[0].name === 'user0');
assert(user3Follows.length === 1);

// Same but with ID instead of object.
{
  const user0Follows = (await User.findOne({
    where: {id: user0.id},
    include: [{model: User, as: 'Follows'}],
  })).Follows
  assert(user0Follows[0].name === 'user1');
  assert(user0Follows[1].name === 'user2');
  assert(user0Follows.length === 2);
}

// has methods
assert(!await user0.hasFollow(user0))
assert(!await user0.hasFollow(user0.id))
assert( await user0.hasFollow(user1))
assert( await user0.hasFollow(user2))
assert(!await user0.hasFollow(user3))

// Count method
assert(await user0.countFollows() === 2)

await sequelize.close();
})();

The SQLite table generated is:

CREATE TABLE IF NOT EXISTS `UserFollowUser` (
  `createdAt` DATETIME NOT NULL,
  `updatedAt` DATETIME NOT NULL,=
  `UserId` INTEGER NOT NULL REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  `FollowId` INTEGER NOT NULL REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  PRIMARY KEY (`UserId`, `FollowId`)
);

Also asked at: How to have a self-referencing many-to-many association in Sequelize?

Many-to-many with custom columns

Going back to the user likes post example, we can also achieve the same result with a custom table we create.

This allows us to add extra parameters to the relationship, e.g. here we add a score saying how much the user likes the post.

const assert = require('assert');
const path = require('path');

const { Sequelize, DataTypes } = require('sequelize');

const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: 'db.sqlite3',
});

(async () => {

// Create the tables.
const User = sequelize.define('User', {
  name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
  body: { type: DataTypes.STRING },
}, {});
const UserLikesPost = sequelize.define('UserLikesPost', {
  UserId: {
    type: DataTypes.INTEGER,
    references: {
      model: User,
      key: 'id'
    }
  },
  PostId: {
    type: DataTypes.INTEGER,
    references: {
      model: Post,
      key: 'id'
    }
  },
  score: {
    type: DataTypes.INTEGER,
  },
});
User.belongsToMany(Post, {through: UserLikesPost});
Post.belongsToMany(User, {through: UserLikesPost});
await sequelize.sync({force: true});

// Create some users and likes.

const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})

const post0 = await Post.create({body: 'post0'});
const post1 = await Post.create({body: 'post1'});
const post2 = await Post.create({body: 'post2'});

// Make some useres like some posts.
await user0.addPost(post0, {through: {score: 1}})
await user1.addPost(post1, {through: {score: 2}})
await user1.addPost(post2, {through: {score: 3}})

// Find what user0 likes.
const user0Likes = await user0.getPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[0].UserLikesPost.score === 1);
assert(user0Likes.length === 1);

// Find what user1 likes.
const user1Likes = await user1.getPosts({order: [['body', 'ASC']]})
assert(user1Likes[0].body === 'post1');
assert(user1Likes[0].UserLikesPost.score === 2);
assert(user1Likes[1].body === 'post2');
assert(user1Likes[1].UserLikesPost.score === 3);
assert(user1Likes.length === 2);

// Where on the custom through table column.
// https://stackoverflow.com/questions/38857156/how-to-query-many-to-many-relationship-sequelize
{
  const user1LikesWithScore3 = await Post.findAll({
    include: [{
      model: User,
      where: {
        id: user1.id
      },
      through: {where: {score: 3}},
    }],
  })
  assert(user1LikesWithScore3[0].body === 'post2');
  assert(user1LikesWithScore3[0].UserLikesPost.score === 3);
  assert(user1LikesWithScore3.length === 1);
}

// TODO: this doesn't work. Possible at all in a single addUsers call?
// Make user0 and user2 like post1
// This method automatically generated.
//await post1.addUsers(
//  [user0, user2],
//  {through: [
//    {score: 2},
//    {score: 3},
//  ]}
//)

await sequelize.close();
})();

This had been asked at:

and is also documented at: https://sequelize.org/master/manual/advanced-many-to-many.html

How to query by the score has been asked at: How to query many to many relationship sequelize?

How to make complex JOIN queries with multiple associations?

Consider for example the use case:

users can follow users, users can create posts, find all posts by users that a given user follows

To solve this, we basically just nest include: statements as shown below:

#!/usr/bin/env node

// Find all posts by users that a given user follows.
// https://stackoverflow.com/questions/42632943/sequelize-multiple-where-clause

const assert = require('assert');
const path = require('path');

const { Sequelize, DataTypes } = require('sequelize');

const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: 'db.sqlite3',
});

(async () => {

// Create the tables.
const User = sequelize.define('User', {
  name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
  body: { type: DataTypes.STRING },
}, {});
User.belongsToMany(User, {through: 'UserFollowUser', as: 'Follows'});
User.hasMany(Post);
Post.belongsTo(User);
await sequelize.sync({force: true});

// Create data.
const users = await User.bulkCreate([
  {name: 'user0'},
  {name: 'user1'},
  {name: 'user2'},
  {name: 'user3'},
])

const posts = await Post.bulkCreate([
  {body: 'body00', UserId: users[0].id},
  {body: 'body01', UserId: users[0].id},
  {body: 'body10', UserId: users[1].id},
  {body: 'body11', UserId: users[1].id},
  {body: 'body20', UserId: users[2].id},
  {body: 'body21', UserId: users[2].id},
  {body: 'body30', UserId: users[3].id},
  {body: 'body31', UserId: users[3].id},
])

await users[0].addFollows([users[1], users[2]])

// Get all posts by authors that user0 follows.
// The posts are placed inside their respetive authors under .Posts
// so we loop to gather all of them.
{
  const user0Follows = (await User.findByPk(users[0].id, {
    include: [
      {
        model: User,
        as: 'Follows',
        include: [
          {
            model: Post,
          }
        ],
      },
    ],
  })).Follows
  const postsFound = []
  for (const followedUser of user0Follows) {
    postsFound.push(...followedUser.Posts)
  }
  postsFound.sort((x, y) => { return x.body < y.body ? -1 : x.body > y.body ? 1 : 0 })
  assert(postsFound[0].body === 'body10')
  assert(postsFound[1].body === 'body11')
  assert(postsFound[2].body === 'body20')
  assert(postsFound[3].body === 'body21')
  assert(postsFound.length === 4)
}

// With ordering, offset and limit.
// The posts are placed inside their respetive authors under .Posts
// The only difference is that posts that we didn't select got removed.

{
  const user0Follows = (await User.findByPk(users[0].id, {
    offset: 1,
    limit: 2,
    // TODO why is this needed? It does try to make a subquery otherwise, and then it doesn't work.
    // https://selleo.com/til/posts/ddesmudzmi-offset-pagination-with-subquery-in-sequelize-
    subQuery: false,
    include: [
      {
        model: User,
        as: 'Follows',
        include: [
          {
            model: Post,
          }
        ],
      },
    ],
  })).Follows
  assert(user0Follows[0].name === 'user1')
  assert(user0Follows[1].name === 'user2')
  assert(user0Follows.length === 2)
  const postsFound = []
  for (const followedUser of user0Follows) {
    postsFound.push(...followedUser.Posts)
  }
  postsFound.sort((x, y) => { return x.body < y.body ? -1 : x.body > y.body ? 1 : 0 })
  // Note that what happens is that some of the
  assert(postsFound[0].body === 'body11')
  assert(postsFound[1].body === 'body20')
  assert(postsFound.length === 2)

  // Same as above, but now with DESC ordering.
  {
    const user0Follows = (await User.findByPk(users[0].id, {
      order: [[
        {model: User, as: 'Follows'},
        Post,
        'body',
        'DESC'
      ]],
      offset: 1,
      limit: 2,
      subQuery: false,
      include: [
        {
          model: User,
          as: 'Follows',
          include: [
            {
              model: Post,
            }
          ],
        },
      ],
    })).Follows
    // Note how user ordering is also reversed from an ASC.
    // it likely takes the use that has the first post.
    assert(user0Follows[0].name === 'user2')
    assert(user0Follows[1].name === 'user1')
    assert(user0Follows.length === 2)
    const postsFound = []
    for (const followedUser of user0Follows) {
      postsFound.push(...followedUser.Posts)
    }
    // In this very specific data case, this would not be needed.
    // because user2 has the second post body and user1 has the first
    // alphabetically.
    postsFound.sort((x, y) => { return x.body < y.body ? 1 : x.body > y.body ? -1 : 0 })
    // Note that what happens is that some of the
    assert(postsFound[0].body === 'body20')
    assert(postsFound[1].body === 'body11')
    assert(postsFound.length === 2)
  }

  // Here user2 would have no post hits due to the limit,
  // so it is entirely pruned from the user list as desired.
  // Otherwise we would fetch a lot of unwanted user data
  // in a large database.
  const user0FollowsLimit2 = (await User.findByPk(users[0].id, {
    limit: 2,
    subQuery: false,
    include: [
      {
        model: User,
        as: 'Follows',
        include: [ { model: Post } ],
      },
    ],
  })).Follows
  assert(user0FollowsLimit2[0].name === 'user1')
  assert(user0FollowsLimit2.length === 1)

  // Get just the count of the posts authored by users followed by user0.
  // attributes: [] excludes all other data from the SELECT of the queries
  // to optimize things a bit.
  // https://stackoverflow.com/questions/37817808/counting-associated-entries-with-sequelize
  {
    const user0Follows = await User.findByPk(users[0].id, {
      attributes: [
        [Sequelize.fn('COUNT', Sequelize.col('Follows.Posts.id')), 'count']
      ],
      include: [
        {
          model: User,
          as: 'Follows',
          attributes: [],
          through: {
            attributes: []
          },
          include: [
            {
              model: Post,
              attributes: [],
            }
          ],
        },
      ],
    })
    assert.strictEqual(user0Follows.dataValues.count, 4);
  }

  // Case in which our post-sorting is needed.
  // TODO: possible to get sequelize to do this for us by returning
  // a flat array directly?
  // Managed with super many to many as shown below.
  // It's not big deal since the LIMITed result should be small,
  // but feels wasteful.
  // https://stackoverflow.com/questions/41502699/return-flat-object-from-sequelize-with-association
  // https://github.com/sequelize/sequelize/issues/4419
  {
    await Post.truncate({restartIdentity: true})
    const posts = await Post.bulkCreate([
      {body: 'body0', UserId: users[0].id},
      {body: 'body1', UserId: users[1].id},
      {body: 'body2', UserId: users[2].id},
      {body: 'body3', UserId: users[3].id},
      {body: 'body4', UserId: users[0].id},
      {body: 'body5', UserId: users[1].id},
      {body: 'body6', UserId: users[2].id},
      {body: 'body7', UserId: users[3].id},
    ])
    const user0Follows = (await User.findByPk(users[0].id, {
      order: [[
        {model: User, as: 'Follows'},
        Post,
        'body',
        'DESC'
      ]],
      subQuery: false,
      include: [
        {
          model: User,
          as: 'Follows',
          include: [
            {
              model: Post,
            }
          ],
        },
      ],
    })).Follows
    assert(user0Follows[0].name === 'user2')
    assert(user0Follows[1].name === 'user1')
    assert(user0Follows.length === 2)
    const postsFound = []
    for (const followedUser of user0Follows) {
      postsFound.push(...followedUser.Posts)
    }
    // We need this here, otherwise we would get all user2 posts first:
    // body6, body2, body5, body1
    postsFound.sort((x, y) => { return x.body < y.body ? 1 : x.body > y.body ? -1 : 0 })
    assert(postsFound[0].body === 'body6')
    assert(postsFound[1].body === 'body5')
    assert(postsFound[2].body === 'body2')
    assert(postsFound[3].body === 'body1')
    assert(postsFound.length === 4)
  }
}

await sequelize.close();
})();

Super many to many to do the "posts by followed users" query without post processing

Super many to many means explicitly setting belongsTo/hasMany between each model and the through table, in addition to the belongsToMany of each model.

This is the only way I found to nicely make the "posts by followed users" query without post processing.

const assert = require('assert');
const path = require('path');

const { Sequelize, DataTypes, Op } = require('sequelize');

const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: 'tmp.' + path.basename(__filename) + '.sqlite',
  define: {
    timestamps: false
  },
});

(async () => {

// Create the tables.
const User = sequelize.define('User', {
  name: { type: DataTypes.STRING },
});
const Post = sequelize.define('Post', {
  body: { type: DataTypes.STRING },
});
const UserFollowUser = sequelize.define('UserFollowUser', {
    UserId: {
      type: DataTypes.INTEGER,
      references: {
        model: User,
        key: 'id'
      }
    },
    FollowId: {
      type: DataTypes.INTEGER,
      references: {
        model: User,
        key: 'id'
      }
    },
  }
);

// Super many to many.
User.belongsToMany(User, {through: UserFollowUser, as: 'Follows'});
UserFollowUser.belongsTo(User)
User.hasMany(UserFollowUser)

User.hasMany(Post);
Post.belongsTo(User);

await sequelize.sync({force: true});

// Create data.
const users = await User.bulkCreate([
  {name: 'user0'},
  {name: 'user1'},
  {name: 'user2'},
  {name: 'user3'},
])
const posts = await Post.bulkCreate([
  {body: 'body0', UserId: users[0].id},
  {body: 'body1', UserId: users[1].id},
  {body: 'body2', UserId: users[2].id},
  {body: 'body3', UserId: users[3].id},
  {body: 'body4', UserId: users[0].id},
  {body: 'body5', UserId: users[1].id},
  {body: 'body6', UserId: users[2].id},
  {body: 'body7', UserId: users[3].id},
])
await users[0].addFollows([users[1], users[2]])

// Get all the posts by authors that user0 follows.
// without any post process sorting. We only managed to to this
// with a super many to many, because that allows us to specify
// a reversed order in the through table with `on`, since we need to
// match with `FollowId` and not `UserId`.
{
  const postsFound = await Post.findAll({
    order: [[
      'body',
      'DESC'
    ]],
    include: [
      {
        model: User,
        attributes: [],
        required: true,
        include: [
          {
            model: UserFollowUser,
            on: {
              FollowId: {[Op.col]: 'User.id' },
            },
            attributes: [],
            where: {UserId: users[0].id},
          }
        ],
      },
    ],
  })
  assert.strictEqual(postsFound[0].body, 'body6')
  assert.strictEqual(postsFound[1].body, 'body5')
  assert.strictEqual(postsFound[2].body, 'body2')
  assert.strictEqual(postsFound[3].body, 'body1')
  assert.strictEqual(postsFound.length, 4)
}

await sequelize.close();
})();

Related: Return flat object from sequelize with association

Aliases: multiple many to many associations between two models

Suppose now that users can both like and follow posts.

To model that, we will have two many-to-many associations between users and posts.

The problem however is that so far we have used the table names User and Post as the identifier for associations.

So to disambiguate this, we are forced to use the as: parameter, which creates a table alias.

const assert = require('assert');
const path = require('path');

const { Sequelize, DataTypes } = require('sequelize');

const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: 'tmp.' + path.basename(__filename) + '.sqlite',
});

(async () => {

// Create the tables.
const User = sequelize.define('User', {
  name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
  body: { type: DataTypes.STRING },
}, {});

User.belongsToMany(Post, {through: 'UserLikesPost', as: 'likedPosts'});
Post.belongsToMany(User, {through: 'UserLikesPost', as: 'likers'});

User.belongsToMany(Post, {through: 'UserFollowsPost', as: 'followedPosts'});
Post.belongsToMany(User, {through: 'UserFollowsPost', as: 'followers'});

await sequelize.sync({force: true});

// Create some users and likes.

const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})

const post0 = await Post.create({body: 'post0'});
const post1 = await Post.create({body: 'post1'});
const post2 = await Post.create({body: 'post2'});

// Autogenerated add* methods

// Setup likes and follows.
await user0.addLikedPost(post0)
await post1.addLikers([user0, user2])
await user1.addFollowedPosts([post0, post1])
await post1.addFollower(user2)

// Autogenerated get* methods

// Get likes by a user.

const user0Likes = await user0.getLikedPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);

const user1Likes = await user1.getLikedPosts({order: [['body', 'ASC']]})
assert(user1Likes.length === 0);

const user2Likes = await user2.getLikedPosts({order: [['body', 'ASC']]})
assert(user2Likes[0].body === 'post1');
assert(user2Likes.length === 1);

// Get users that liked a given post.

const post0Likers = await post0.getLikers({order: [['name', 'ASC']]})
assert(post0Likers[0].name === 'user0');
assert(post0Likers.length === 1);

const post1Likers = await post1.getLikers({order: [['name', 'ASC']]})
assert(post1Likers[0].name === 'user0');
assert(post1Likers[1].name === 'user2');
assert(post1Likers.length === 2);

const post2Likers = await post2.getLikers({order: [['name', 'ASC']]})
assert(post2Likers.length === 0);

// Get follows by a user.

const user0Follows = await user0.getFollowedPosts({order: [['body', 'ASC']]})
assert(user0Follows.length === 0);

const user1Follows = await user1.getFollowedPosts({order: [['body', 'ASC']]})
assert(user1Follows[0].body === 'post0');
assert(user1Follows[1].body === 'post1');
assert(user1Follows.length === 2);

const user2Follows = await user2.getFollowedPosts({order: [['body', 'ASC']]})
assert(user2Follows[0].body === 'post1');
assert(user2Follows.length === 1);

// Get users that followed a given post.

const post0Followers = await post0.getFollowers({order: [['name', 'ASC']]})
assert(post0Followers[0].name === 'user1');
assert(post0Followers.length === 1);

const post1Followers = await post1.getFollowers({order: [['name', 'ASC']]})
assert(post1Followers[0].name === 'user1');
assert(post1Followers[1].name === 'user2');
assert(post1Followers.length === 2);

const post2Followers = await post2.getFollowers({order: [['name', 'ASC']]})
assert(post2Followers.length === 0);

// Same as getLikedPosts but with the user ID instead of the model object.
{
  const user0Likes = await Post.findAll({
    include: [{
      model: User,
      as: 'likers',
      where: {id: user0.id},
    }],
    order: [['body', 'ASC']],
  })
  assert(user0Likes[0].body === 'post0');
  assert(user0Likes[1].body === 'post1');
  assert(user0Likes.length === 2);
}

// Yet another way that can be more useful in nested includes.
{
  const user0Likes = (await User.findOne({
    where: {id: user0.id},
    include: [{
      model: Post,
      as: 'likedPosts',
    }],
    order: [[{model: Post, as: 'likedPosts'}, 'body', 'ASC']],
  })).likedPosts
  assert(user0Likes[0].body === 'post0');
  assert(user0Likes[1].body === 'post1');
  assert(user0Likes.length === 2);
}

await sequelize.close();
})();

Bonus: aliased many-to-one requires foreignKey

Aliased many-to-many works without foreignKey. But many-to-one doesn't: it would create two separate IDs e.g. UserId and authorId.

This is the only way I can get it to work (suppose each post has exactly one author and one reviewer):

User.hasMany(Post, {as: 'authoredPosts', foreignKey: 'authorId'});
Post.belongsTo(User, {as: 'author', foreignKey: 'authorId'});

User.hasMany(Post, {as: 'reviewedPosts', foreignKey: 'reviewerId'});
Post.belongsTo(User, {as: 'reviewer', foreignKey: 'reviewerId'});

How to list all the automatically generated methods?

How to display all methods of an object? works for this.

JOIN + GROUP BY + an aggregate like COUNT

See: Sequelize query with count in inner join

Tested on Ubuntu 21.04, node.js v14.17.0.

M:M relation through table BookArticles:

m.Book.belongsToMany(m.Article, {through: m.BookArticles});
m.Article.belongsToMany(m.Books, {through: m.BookArticles});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top