Sequelize: addUser is not a function

Viewed 944

I'm learning to use Sequelize, and I'm stumped. I have two models, User and Salon, with a N:M relationship between them, mediated by an auxiliary table, UsersSalons (So an user might manage many salons, for a franchise for example, or a salon may be managed by many employees)

When creating a new salon, my intention is to associate the logged in user to it. However, while I get to save the new salon in the database, it never gets to be associated to the user, returning this error:

ERROR PUT /salons Error: TypeError: salon.addUser is not a function

When Googling this, it appears that the common cause of this error is trying to apply the function to the whole model class instead of an instance of it, but this is not what is occurring here.

Here's the PUT /salons route:

router.put('/', checkLoggedIn, (req, res, next) => {
  const user = User.findOne({ where: { id: req.user[0].id } })
    .then(() =>
      Salon.create({
        name: req.body.name,
        street: req.body.street,
        number: req.body.number,
        zipcode: req.body.zipcode,
        town: req.body.town,
        province: req.body.province,
        addressComplements: req.body.addressComplements,
        phoneNumber: req.body.phoneNumber,
      })
    )
    .then((salon) => {
      console.log(salon)
      salon.addUser(user) //and here is where the error happens
    })
    .then((salon) => res.status(200).json(salon))
    .catch((err) => next(new Error(err)))
})

Just in case, here are the User and Salon models, as well as how the UsersSalons table has been created:

'use strict'
const { Model } = require('sequelize')
module.exports = (sequelize, DataTypes) => {
  class Salon extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      Salon.belongsToMany(models.User, {
        through: 'UsersSalons',
        as: 'salon',
        foreignKey: 'salonId',
        otherKey: 'userId',
      })
    }
  }
  Salon.init(
    {
      name: { type: DataTypes.STRING, allowNull: false, unique: true },
      street: { type: DataTypes.STRING, allowNull: false },
      number: { type: DataTypes.STRING, allowNull: false },
      zipcode: { type: DataTypes.STRING, allowNull: false },
      town: { type: DataTypes.STRING, allowNull: false },
      province: { type: DataTypes.STRING, allowNull: false },
      addressComplements: DataTypes.STRING,
      phoneNumber: {
        type: DataTypes.STRING,
        allowNull: false,
      },
    },
    {
      sequelize,
      modelName: 'Salon',
    }
  )
  return Salon
}
'use strict'
const { Model } = require('sequelize')

module.exports = (sequelize, DataTypes) => {
  class User extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      User.belongsToMany(models.Salon, {
        through: 'UsersSalons',
        as: 'user',
        foreignKey: 'userId',
        otherKey: 'salonId',
      })
    }
  }
  User.init(
    {
      email: {
        type: DataTypes.STRING,
        validate: { isEmail: true },
        allowNull: false,
        unique: true,
      },
      firstName: { type: DataTypes.STRING, allowNull: false },
      lastName: { type: DataTypes.STRING, allowNull: false },
      isActive: { type: DataTypes.BOOLEAN, defaultValue: false },
      password: { type: DataTypes.STRING },
      confirmationCode: DataTypes.STRING,
    },
    {
      sequelize,
      modelName: 'User',
    }
  )
  return User
}
'use strict'

module.exports = {
  up: async (queryInterface, Sequelize) => {
    return queryInterface.createTable('UsersSalons', {
      createdAt: { allowNull: false, type: Sequelize.DATE },
      updatedAt: { allowNull: false, type: Sequelize.DATE },
      userId: { type: Sequelize.INTEGER, primaryKey: true },
      salonId: { type: Sequelize.INTEGER, primaryKey: true },
    })
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('UsersSalons')
  },
}

EDIT: After trying out Anatoly's suggestion, there's still errors going on. Here's the updated code for the PUT /salons route and the output:

router.put('/', checkLoggedIn, (req, res, next) => {
  const user = User.findOne({ where: { id: req.user[0].id } })
    .then(() => {
      return Salon.create({
        name: req.body.name,
        street: req.body.street,
        number: req.body.number,
        zipcode: req.body.zipcode,
        town: req.body.town,
        province: req.body.province,
        addressComplements: req.body.addressComplements,
        phoneNumber: req.body.phoneNumber,
      })
    })
    .then((salon) => {
      console.log(`salon output after creation: ${salon}`)
      return salon.addUser(user)
    })
    .then((salon) => res.status(200).json(salon))
    .catch((err) => next(new Error(err)))
})
Executing (default): SELECT "id", "email", "firstName", "lastName", "isActive", "password", "confirmationCode", "createdAt", "updatedAt" FROM "Users" AS "User" WHERE "User"."id" = 1;
Executing (default): SELECT "id", "email", "firstName", "lastName", "isActive", "password", "confirmationCode", "createdAt", "updatedAt" FROM "Users" AS "User" WHERE "User"."id" = 1;
Executing (default): INSERT INTO "Salons" ("id","name","street","number","zipcode","town","province","phoneNumber","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING "id","name","street","number","zipcode","town","province","addressComplements","phoneNumber","createdAt","updatedAt";
salon output after creation: [object SequelizeInstance:Salon]
Executing (default): SELECT "createdAt", "updatedAt", "salonId", "userId" FROM "UsersSalons" AS "UsersSalons" WHERE "UsersSalons"."salonId" = 15 AND "UsersSalons"."userId" IN ('[object Promise]');
ERROR PUT /salons Error: SequelizeDatabaseError: invalid input syntax for integer: "[object Promise]"
    at [project route]/routes/salons.routes.js:34:26
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
PUT /salons 500 860.025 ms - 39
2 Answers

You mixed up aliases in belongsToMany associations. You should give a name to an alias that is related to a model that passed as the first argument to belongsToMany:

Salon.belongsToMany(models.User, {
        through: 'UsersSalons',
        as: 'user',
        foreignKey: 'salonId',
        otherKey: 'userId',
      })
User.belongsToMany(models.Salon, {
        through: 'UsersSalons',
        as: 'salon',
        foreignKey: 'userId',
        otherKey: 'salonId',
      })

Also you didn't return found user and created salon instance from then handler. Should be like this:

.then((user) =>
      return Salon.create({
        name: req.body.name,
        street: req.body.street,
        number: req.body.number,
        zipcode: req.body.zipcode,
        town: req.body.town,
        province: req.body.province,
        addressComplements: req.body.addressComplements,
        phoneNumber: req.body.phoneNumber,
      }).then((salon) => {
        console.log(salon)
        salon.addUser(user.id)
        return salon
     })
    )
    .then((salon) => res.status(200).json(salon))
    .catch((err) => next(new Error(err)))

Found the solution, based on Anatoly's answer, but simpler. If we have the user's ID in req.user[0], and that's enough to associate our new salon to it, we do not need to search again in our database for said user.

This would be the final code snippet:

router.put('/', checkLoggedIn, (req, res, next) => {
  Salon.create({
    name: req.body.name,
    street: req.body.street,
    number: req.body.number,
    zipcode: req.body.zipcode,
    town: req.body.town,
    province: req.body.province,
    addressComplements: req.body.addressComplements,
    phoneNumber: req.body.phoneNumber,
  })
    .then((salon) => {
      salon.addUser(req.user[0].id)
      return salon
    })
    .then((salon) => res.status(200).json(salon))
    .catch((err) => next(new Error(err)))
})

Again, thank you, Anatoly, for the help. It has been invaluable.

Related