Search code examples
mysqlsequelize.jsassociations

Sequelize M:N association not creating through table record


When I create a recipe with associated tags using the through option, no record is created in the joining table in the mysql database I'm connected to.

Here are my models definitions:

export const Recipe = sequelize.define('Recipe', {
    // Model attributes are defined here
    title: {
        type: DataTypes.STRING,
        allowNull: false
    },
    image: {
        type: DataTypes.STRING,
        allowNull: true
    },
    prepTime: {
        type: DataTypes.DOUBLE,
        allowNull: false
    },
    cookTime: {
        type: DataTypes.DOUBLE,
        allowNull: false
    },
    totalTime: {
        type: DataTypes.DOUBLE,
        allowNull: false
    },
    servings: {
        type: DataTypes.INTEGER,
        allowNull: false
    },
    rating: {
        type: DataTypes.INTEGER,
        allowNull: false
    },
    notes: {
        type: DataTypes.STRING, allowNull: true
    },
}, {
    // Other model options go here
    tableName: 'Recipes'
});

export const Tag = sequelize.define('Tag', {
    // Model attributes are defined here
    name: {
        type: DataTypes.STRING,
        allowNull: false
    },
}, {
    // Other model options go here
    tableName: 'Tags'
});

export const RecipeTag = sequelize.define('RecipeTag', {
    // Model attributes are defined here
}, {
    // Other model options go here
    timestamps: false,
    tableName: 'RecipeTags'
});

Here are my associations:

Recipe.belongsToMany(Tag, {
    through: RecipeTag,
    foreignKey: 'recipeId',
    as: 'tags'
})

Tag.belongsToMany(Recipe, {
    through: RecipeTag,
    foreignKey: 'tagId',
    as: 'recipes'
})

Here is the create call:

Recipe.create(args, {
                model: Tag,
                through: RecipeTag,
                as: 'tags'
            });

And here is the data:

{
  "title": "Test Recipe",
  "image": "test",
  "prepTime": 20,
  "cookTime": 40,
  "totalTime": 60,
  "servings": 2,
  "rating": 5,
  "categoryId": 1,
  "tags": [
    {
      "name": "New tag",
      "id": 1
    }
  ],
}

With this set up the create method only creates a new recipe. How can I use the create method to add a record to the joining RecipeTags table at the same time as creating a new recipe? I've managed to get it working by doing something like this:

args.tags.map(async (tag: { tagId: number }) => {
    await RecipeTag.create({tagId: tag.tagId, recipeId: recipe.id})
});

But I'd rather have it done in the create if it's possible.


Solution

  • You need to wrap the association options with include.

    Recipe.create(args, {
        include: {
            model: Tag,
            through: RecipeTag,
            as: 'tags'
        }
    });
    

    UPDATE:

    In order to prevent the duplicates, you can add ignoreDuplicates option and data must include the primary key value.

    {
      "title": "Test Recipe",
      ...
      "tags": [
        {
          "name": "New tag",
          "id": 1   # this is important
        }
      ]
    }
    

    Then

    Recipe.create(args, {
        include: {
            model: Tag,
            through: RecipeTag,
            as: 'tags',
            ignoreDuplicates: true  // Add this
        }
    });
    

    There were some bugs for this option, I suggest you to use the newer version of Sequelize, if you haven't updated lately.