How to implement Instance Methods in Sequelize 6 with Node.js - 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.

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

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

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',
}

Related

Nodejs and sequelize : Error .find is not a function

I am a nodeJs developer, I failed to get the list of users, when I tested my code on postman it gives me the error below:
User.find is not a function
The following is my user.js file
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class User extends Model {
static associate(models) {
}
}
User.init({
username: {
type:DataTypes.STRING,
allowNull: false,
},
email: {
type:DataTypes.STRING,
allowNull: false,
},
password: {
type:DataTypes.STRING,
allowNull: false,
},
role: {
type:DataTypes.STRING,
allowNull: false,
},
}, {
sequelize,
tableName:'users',
modelName: 'User',
});
return User;
};
and this is where it's imported/required
const router = require('express').Router();
let User = require('../models/user');
router.route('/getUser').post((req, res) => {
User.find({})
.then(users => res.json(users))
.catch(err => res.status(400).json('Error: ', err));
});
The user.js file that you provided is exporting a function that create the User class (and not actually the User model class)
Did you already init this class elsewhere and you are importing the wrong file ?
It didn't work, I did as you told me but it gives me back:
sequelize id not defined
Also I don't think that the problem is in the model because I have used it before and in the login I used .find and it worked without any problem!
this login code :
router.post('/login', async (req,res,next) => {
const user = await User.findOne({ where: { email: req.body.email }});
if (user) {
const password_valid = await bcrypt.compare(req.body.password,user.password);
if (password_valid) {
token = JWT.sign({
isLogin: true,
"id": user.id,
"email": user.email,
"username": user.username
},"12345678");
res.status(200).json({token :token});
} else {
res.status(400).json({isLogin : false, error: "password incorrect" });
}
} else {
res.status(404).json({isLogin : false, error :"User does not exist"});
}
});

Sequelize class method works fine but instance method is not working

I've defined a user model in Sequelize, and also defined a custom class method and an instance method for it. I'm calling these two methods in my login api (which works fine). The problem is that the class method works perfectly, but the instance method results an error, and I cannot recognize what is wrong with my code. PLEASE HELP.
This is my user model and its methods:
const Sequelize = require("sequelize");
const sequelize = require("../db/db.config");
const bcrypt = require("bcryptjs");
const _ = require("lodash");
const jwt = require("jsonwebtoken");
const User = sequelize.define("user", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
first_name: {
type: Sequelize.STRING(50),
allowNull: false,
},
last_name: {
type: Sequelize.STRING(50),
allowNull: false,
},
email: {
type: Sequelize.STRING(50),
allowNull: false,
validate: {
isEmail: true,
},
},
password: {
type: Sequelize.STRING(100),
allowNull: false,
},
});
User.prototype.testMethod = function () {
console.log("THIS IS A TEST");
};
User.beforeCreate(async (user, options) => {
const hashedPassword = await bcrypt.hash(user.password, 10);
user.password = hashedPassword;
});
User.findByEmailAndPassword = async function (inputEmail, inputPassword) {
try {
const user = await User.findOne({ where: { email: inputEmail } });
if (user === null) {
return null;
}
const passwordMatch = await bcrypt.compare(inputPassword, user.password);
if (!passwordMatch) {
return null;
}
return _.pick(user, "id", "first_name", "last_name", "email");
} catch (error) {
console.log("FIND BY EMAIL AND PASSWORD ERROR: ", error);
}
};
module.exports = User;
And this is my login router:
const express = require("express");
const router = express.Router();
const User = require("../models/user.model");
router.post("/api/login", async (req, res) => {
try {
const user = await User.findByEmailAndPassword(
req.body.email,
req.body.password
);
console.log("USER: ", user);
await user.testMethod();
if (!user) {
return res.status(400).send({
errorMessage: "Username and password combination is not correct!",
});
}
return res.status(200).send(user);
} catch (error) {
res.status(400).send({ errorMessage: error });
}
});
module.exports = router;
Thanks.
First, the reason that you get the issue is that findByEmailAndPassword is returning the regular object from _.pick and you are defining the instance method for Sequelize instance. This instance method can be callable on Sequelize instance and not on regular object.
However, your goal is
What I'm trying to do here is to avoid sending user password in my response body.
defaultScope is great for this use case. It allows you to define some repetitive options on a model.
You can define your User model as
const User = sequelize.define("user", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
...
}, {
defaultScope: {
attributes: {
exclude: ['password']
}
}
});
Defining the defaultScope on the model, this will be applied to many Sequelize functions by default.
Scopes apply to .find, .findAll, .count, .update, .increment and .destroy.
I also tested that it also applied to .findByPk, .findOne.
So, how to use...
Call regular Sequelize findOne function.
const user = User.findOne({
where: {
email: req.body.email,
password: req.body.password
}
});
By default, since defaultScope is applied, this won't return password in response.
In some scenarios where you need to return the password, use unscoped to disable the defaultScope.
// This will return `password` in response.
User.unscoped().fineOne(...)
For reference: https://sequelize.org/master/manual/scopes.html

Getting sequelize attribute notNull violation in my create controller due to variable name being different from column name

Im trying to add users into my database using bcrypt so I can has their passwords, but when I set the req.body to the bcrypt variable I get a notNull Violation: Users.user_password cannot be null.
This is my model I used to define my users table
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Users 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
}
};
Users.init({
user_id:{
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
user_name:{
type: DataTypes.STRING,
allowNull: false
},
user_email:{
type: DataTypes.STRING,
allowNull: false
},
user_password:{
type: DataTypes.STRING,
allowNull: false
},
}, {
sequelize,
timestamps: false,
modelName: 'Users',
tableName: 'users'
});
return Users;
};
This is the controller I'm using to add a new user. At the bottom when I pass in the bcryptPassword variable instead of the user_password thats deconstructed at the top, I get the notNull Violation. But if I dont pass in the bcryptPassword variable I can create a new user. Does anyone know why this is happening? Is there a way to config the model so I can use any vairable? Any help would be greatly appreciated!
Thanks!
const { Users } = require('../models');
const bcrypt = require('bcrypt');
module.exports = {
createUser: async(req, res) => {
const { user_name, user_email, user_password } = req.body;
try {
const salt = await bcrypt.genSalt(10);
const bcryptPassword = await bcrypt.hash(user_password, salt)
const newUser = await Users.create({ user_name, user_email, bcryptPassword });
return res.json(newUser);
} catch (error) {
console.error(error.message);
return res.status(500).json(error);
}
}
}
I found a viable answer while searching through the sequelize documentation.
https://sequelize.org/master/manual/getters-setters-virtuals.html
I used the set() function in my user_password field and that worked. Every time I created a new user the password was hashed in the database using bcrypt.
Here is the modified model code. Changes are marked by asterics.
'use strict';
*****const bcrypt = require('bcrypt');*****
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Users 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
}
};
Users.init({
user_id:{
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
user_name:{
type: DataTypes.STRING,
allowNull: false
},
user_email:{
type: DataTypes.STRING,
allowNull: false
},
user_password:{
type: DataTypes.STRING,
allowNull: false,
*****set(value) {
const hash = bcrypt.hashSync(value, 10);
this.setDataValue('user_password', hash);
}*****
},
}, {
sequelize,
timestamps: false,
modelName: 'Users',
tableName: 'users',
});
return Users;
};

How to set automatic password hashing in Sequelize's user model?

Right now I'm hashing the password on the route function and providing the hash when creating the user but I know there's a way to have this be handled through Sequelize itself. I have searched around but every answer seems to be outdated or the methods never got called. Here is my config:
server/models/User.js
module.exports = (sequelize, type) => {
const User = sequelize.define(
"User",
{
user_id: {
type: type.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: type.STRING,
allowNull: false
},
email: {
type: type.STRING,
allowNull: false
},
password: {
type: type.STRING,
allowNull: false
},
reg_date: {
type: type.DATEONLY,
allowNull: false,
defaultValue: sequelize.fn("now")
}
},
{
timestamps: false
}
);
return User;
};
server/config/sequelize.js
const Sequelize = require("sequelize");
const sequelize = new Sequelize(process.env.CLEARDB_DATABASE_URL);
sequelize
.authenticate()
.then(() => {
console.log("Connection has been established successfully.");
})
.catch(err => {
console.error("Unable to connect to the database:", err);
});
const UserModel = require("../models/User");
const User = UserModel(sequelize, Sequelize);
module.exports = User;
This is how I'm handling hashing right now:
server/routes/register.js
User.findOne({ where: { email: email } }).then(user => {
if (!user) {
bcrypt.hash(password, 10, (err, hash) => {
if (err) throw err;
User.create({
name: req.body.name,
email: email,
password: hash
})
.then(user => {
return user;
})
.catch(err => console.log(err));
});
}
});
This did the trick:
...
{
timestamps: false
}
);
User.addHook(
"beforeCreate",
user => (user.password = bcrypt.hashSync(user.password, 10))
);
return User;
};
Please don't mix your model definition with a business or a security logic (or another one). In future you might want to change an encryption library or a hashing algorithm and you will have to change your model accordingly. A security layer should be separated from your models.

sequelize use instance method after selecting result

I want to understand how sequelize instance methods works and if its possible to use returned object as instance for further usage. Basically I'm just selecting user by its user name, later I want to compare if password matches and if so - update data. But the error says
Unhandled rejection TypeError: user_data.validPassword is not a function
and I'm not even close to instance update..
my User model:
const bcrypt = require('bcryptjs');
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
user_name: DataTypes.STRING,
user_password: DataTypes.STRING,
user_token: DataTypes.STRING,
user_alias_name: DataTypes.STRING,
}, {
tableName: 'oc_users',
instanceMethods: {
generateHash(password) {
return bcrypt.hash(password, bcrypt.genSaltSync(8));
},
validPassword(password) {
return bcrypt.compare(password, this.password);
}
}
});
return User;
};
my method:
...
loginAttempt(cookie) {
return models.User.findOne({
attributes: ['id', 'user_password', 'user_alias_name'],
where: {user_name: this.user}
}).then(user_data => {
if (!user_data) return 'No matching results for such a user';
return user_data.validPassword(this.password).then(result => {
if (result !== true) return 'Invalid password for selected user';
return this.updateAfterLogin(user_data, cookie);
})
})
}
updateAfterLogin(user, cookie) {
return user.update({
user_token: cookie
}).then(()=> {
return {data: 'updated'};
})
}
...
It depends on which version of sequelize you're using and probabily you're using Sequelize v4. On Sequelize v4 classMethods and instanceMethods were removed from sequelize.define.
You may check it at oficial docs for more informations:
http://docs.sequelizejs.com/manual/tutorial/upgrade-to-v4.html#config-options
Removed classMethods and instanceMethods options from sequelize.define. Sequelize models are now ES6 classes. You can set class / instance level methods like this
Old
const Model = sequelize.define('Model', {
...
}, {
classMethods: {
associate: function (model) {...}
},
instanceMethods: {
someMethod: function () { ...}
}
});
New
const Model = sequelize.define('Model', {
...
});
// Class Method
Model.associate = function (models) {
...associate the models
};
// Instance Method
Model.prototype.someMethod = function () {..}

Resources