Jeffrey Dabo
Jeffrey Dabo

Reputation: 227

How to implement Instance Methods in Sequelize 6 with Node.js

So i was implementing a bit of change in my server application - switching databases from MongoDb to PostgreSQL (with Sequelize 6) and i had to change the controller functions i had created for mongoose to suit the current database but there was a problem implementing the instance methods, but as usual there was little to no helpful solutions online for this with Sequelize 6. But now there is. Below are the code samples for some of the problems and error messages you may be facing if you come across this post.

The function which calls the instance method: userController.js (login function)

User.findByPk(req.body.id)
    .then(user => {
        if (!user) {
            return res.status(401).send('Sorry!! You do not have an account with us.')
        }

        if (!user.validPassword(req.body.password)) {
            return res.status(401).send('Invalid Password')
        } else {
            res.status(200).json({ user })
        }
    })
    .catch(error => {
        console.log(error)
        res.status(500).send({
            message: 'Some error occurred while logging in this User',
            error: error.message
        });
    });

EXAMPLE CODE 1

user.js

'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) {
            // define association here
        }
    };

    User.init({
        username: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true
        },
        password: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true
        },
        password_confirmation: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true
        }
    }, {
        hooks: {
            beforeCreate: (User) => {
                const salt = bcrypt.genSaltSync();
                User.password = bcrypt.hashSync(User.password, salt);
                User.password_confirmation = User.password;
            }
        },
        instanceMethods: {
            validatePassword: (password) => {
                return bcrypt.compareSync(password, this.password);
            }
        }
        sequelize,
        modelName: 'User',
    });
    return User;
};

response (Server 500 error message)

{
    "message": "Some error occurred while logging in this User",
    "error": "user.validPassword is not a function"
}

The instance method in this case is not recognized and the function validPassword() is not run thus a 500 Server error. Let's move to example 2.

EXAMPLE CODE 2

user.js

'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) {
            // define association here
        }
    };

    User.init({
        username: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true
        },
        password: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true
        },
        password_confirmation: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true
        }
    }, {
        hooks: {
            beforeCreate: (User) => {
                const salt = bcrypt.genSaltSync();
                User.password = bcrypt.hashSync(User.password, salt);
                User.password_confirmation = User.password;
            }
        },
        instanceMethods: {
            validatePassword: (password) => {
                return bcrypt.compareSync(password, this.password);
            }
        }
        sequelize,
        modelName: 'User',
    });

    User.prototype.validPassword = (password) => {
        return bcrypt.compareSync(password, this.password);
    };

    return User;
};

response (Server 500 error message)

{
    "message": "Some error occurred while logging in this User",
    "error": "Illegal arguments: string, undefined"
}

The instance method in this case is still not recognized and the function validPassword() is thus not run because over here the parameter this.password for the bcrypt.compareSync() function is not defined (or has been used outside the Model extension) thus a 500 Server error.

And now for the solution.

Upvotes: 5

Views: 6182

Answers (3)

I don't think this will help you but I can see that you have a syntax error in your codes, try adding , before

sequelize, modelName: 'User'

 {
    hooks: {
        beforeCreate: (User) => {
            const salt = bcrypt.genSaltSync();
            User.password = bcrypt.hashSync(User.password, salt);
            User.password_confirmation = User.password;
        }
    },
    instanceMethods: {
        validatePassword: (password) => {
            return bcrypt.compareSync(password, this.password);
        }
    },
    sequelize,
    modelName: 'User',
}

Upvotes: 1

Calin
Calin

Reputation: 81

After around half a day of searching, I found out that for some reason the instanceMethods functionality has been removed in Sequelize v4. As a result, the only way to obtain this functionality is by one of the following:

  • declaring the function on the model class as Jeffrey Dabo suggested
  • adding the function on the prototype of the Sequelize model

Very important: If you go with the prototype approach, in order to have access to the this object, you need to declare the function using the function syntax and not as an arrow function, or else it will not work.

Example:

const User = sequelize.define('User', {...});

User.prototype.validatePassword = function (password) {
  return bcrypt.compareSync(password, this.password);
}

Upvotes: 8

Jeffrey Dabo
Jeffrey Dabo

Reputation: 227

Placing the instance method function right under the class method function (associations on models) would eventually allow your function validPassword() to be recognized, run and produce the desired response.

user.js

'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) {
            // define association here
        }

        validPassword(password) => {
            return bcrypt.compareSync(password, this.password);
        };
    };

    User.init({
        username: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true
        },
        password: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true
        },
        password_confirmation: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true
        }
    }, {
        hooks: {
            beforeCreate: (User) => {
                const salt = bcrypt.genSaltSync();
                User.password = bcrypt.hashSync(User.password, salt);
                User.password_confirmation = User.password;
            }
        },
        instanceMethods: {
            validatePassword: (password) => {
                return bcrypt.compareSync(password, this.password);
            }
        }
        sequelize,
        modelName: 'User',
    });

    return User;
};

Upvotes: 1

Related Questions