So I set up a Sequelize client to work with my MariaDB instance. The connection is fine, the tables exist, and everything is working expectedly, except for when I try to add a User with the User.create method. I expect that the validation would work as expected, and a user would be created when non-null values are passed.
Here I'm initializing the User class:
class User extends Model {
declare id: number;
declare username: string;
declare password: string;
declare role: string;
declare disabled: boolean;
declare email?: string;
declare phone_number?: string;
declare interests?: string;
declare first_name?: string;
declare last_name?: string;
constructor() {
super();
}
public comparePassword = async (password: string): Promise<boolean> => {
return await bc.compare(password, this.password);
};
public getPublic = (): User => {
const { password, ...publicUser } = this.get();
return publicUser as User;
};
}
Then here I'm using an initUsers function to actually initialize the users table:
export const initUsers = async () => {
User.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
username: {
type: new DataTypes.STRING(128),
allowNull: false,
},
password: {
type: new DataTypes.STRING(128),
allowNull: false,
},
role: {
type: new DataTypes.STRING(128),
allowNull: false,
},
disabled: {
type: DataTypes.BOOLEAN,
allowNull: false,
},
email: {
type: new DataTypes.STRING(128),
allowNull: true,
},
phone_number: {
type: new DataTypes.STRING(128),
allowNull: true,
},
interests: {
type: new DataTypes.STRING(128),
allowNull: true,
},
first_name: {
type: new DataTypes.STRING(128),
allowNull: true,
},
last_name: {
type: new DataTypes.STRING(128),
allowNull: true,
},
},
{
tableName: "users",
sequelize: db.sequelize,
modelName: "User",
timestamps: true,
freezeTableName: true,
}
);
User.create({
username: "admin",
password: await bc.hash("admin", 10),
role: "superuser",
disabled: false,
});
};
You can see that I've also added a manual User.create call, that, if working as expected, would create a user with the username "admin", a hashed password of the same value, etc. These 4 fields are marked as non-nullable, however, as you can see, the values I pass are not null.
I still, however, receive the following error when this User.create method runs:
SequelizeValidationError: notNull Violation: User.username cannot be null,
notNull Violation: User.password cannot be null,
notNull Violation: User.role cannot be null,
notNull Violation: User.disabled cannot be null
at InstanceValidator._validate (C:\VVibrant Web Solutions\Onyx Core\node_modules\sequelize\src\instance-validator.js:78:13)
at InstanceValidator._validateAndRunHooks (C:\VVibrant Web Solutions\Onyx Core\node_modules\sequelize\src\instance-validator.js:111:7)
at InstanceValidator.validate (C:\VVibrant Web Solutions\Onyx Core\node_modules\sequelize\src\instance-validator.js:93:12)
at User.save (C:\VVibrant Web Solutions\Onyx Core\node_modules\sequelize\src\model.js:3996:7)
at Function.create (C:\VVibrant Web Solutions\Onyx Core\node_modules\sequelize\src\model.js:2280:12)
[ERROR] 01:26:21 SequelizeValidationError: notNull Violation: User.username cannot be null,
notNull Violation: User.password cannot be null,
notNull Violation: User.role cannot be null,
notNull Violation: User.disabled cannot be null
When I console.log the value of the User.create method. It returns the async function I'd expect:
console.log("User", User.create);
// output: User [AsyncFunction: create]
console.log("User", User === db.sequelize.models.User);
// output: true
Here is some more context that might be helpful. Here is how I'm initializing the sequelize instance in a separate db.ts file:
export const db: {
sequelize: Sequelize;
} = {
sequelize: new Sequelize({
dialect: "mariadb",
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || "3306"),
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
logging: false,
sync: { force: true },
}),
};
export const initDB = async () => {
await db.sequelize.authenticate();
await initUsers();
await initTokens();
await User.sync({ force: true });
await Token.sync({ force: true });
};
Like I said, the sequelize instance itself seems to be working as expected. The tables show up in the database, they have all the expected columns, although there are no entries.
So I found a fix. It turns out the culprit was adding the
constructor() {
super();
}
to the User class. Not sure why this is the case, but removing that fixed the problem. Still happy if anyone can provide an explanation for why this breaks it, but this solves my issue for now.
Related
I was trying to connect and get data from oracle database using sequelize, but it was returning an error Cannot read properties of undefined (reading 'length'): undefined
Here is the code :
Connection
import { Sequelize, DataTypes } from "sequelize";
import _projects from "../models/projects.js";
export const getProjects = async (req, res, next) => {
var conn = new Sequelize({
dialect: 'oracle',
username: dbAdmin,
password: dbPass,
dialectOptions: { connectString: connStr } // also tried { connectionString: connStr }
});
function initModel(connection) {
const projects = _projects.init(connection, DataTypes);
return { projects };
}
var db = initModel(conn);
const all_projects = await db.projects.findAll()
console.log("all_projects", all_projects.rows); // Cannot read properties of undefined (reading 'length'): undefined
}
projects.js
import _sequelize from 'sequelize';
const { Model, Sequelize } = _sequelize;
export default class projects extends Model {
static init(sequelize, DataTypes) {
return super.init({
id: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true
},
credat: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.fn('getdate')
},
description: {
type: DataTypes.STRING(1024),
allowNull: true
},
picture: {
type: DataTypes.STRING(255),
allowNull: true
},
settings: {
type: DataTypes.TEXT,
allowNull: true
},
is_deleted: {
type: DataTypes.INTEGER,
allowNull: true
}
}, {
sequelize,
tableName: 'projects',
schema: 'dbo',
timestamps: false,
});
}
}
It seems like you confused findAll with findAndCountAll which returns rowa and count and also you didn't indicate any parameters at all (which can lead to this error with undefined) in findAll:
Compare:
const all_projects = await db.projects.findAll({})
console.log("all_projects", all_projects);
and
const a_page_of_projects = await db.projects.findAndCountAll({
limit: 10,
offset: 0
})
console.log("a_page_of_projects rows", a_page_of_projects.rows);
console.log("a_page_of_projects count", a_page_of_projects.count);
I am trying to create a many to many relationship between user table and role table through userroles table.
After table creation the db looks just fine, I tried pretty much everything I found on the sequelize documentation and previous answers here, nothing seems to work.
I am getting this error: EagerLoadingError [SequelizeEagerLoadingError]: UserRoles is not associated to User!
Any idea of what am I doing wrong ? please help!
class User extends Model {
static associate(models) {
User.belongsToMany(models.Role, {
foreignKey: "user_id",
through:'UserRoles',
as:"users"
});
}
}
User.init(
{
user_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
unique: true,
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
phone: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: "User",
}
);
class Role extends Model {
static associate(models) {
Role.belongsToMany(models.User, {
foreignKey: "role_id",
through:'UserRoles',
as:"roles"
});
}
}
Role.init(
{
role_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
unique:true
},
role_name: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
role_desc: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: "Role",
}
);
class UserRoles extends Model {
static associate(models) {
}
}
UserRoles.init(
{
userroles_id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
// user_id: {
// type: DataTypes.UUID,
// defaultValue: DataTypes.UUIDV4,
// },
// role_id: {
// type: DataTypes.UUID,
// defaultValue: DataTypes.UUIDV4,
// },
},
{
sequelize,
modelName: "UserRoles",
}
);
const signup = (req, res) => {
console.log(req.body);
console.log("signup entry");
if (
!req.body.role ||
!req.body.email ||
!req.body.password ||
!req.body.name ||
!req.body.phone
) {
res.status(400).send({
msg: "Please pass role, email, password and name.",
});
} else {
sequelize.models.User.findOne({
where: {
email: req.body.email,
},
})
.then((duplicateemailfound) => {
if (duplicateemailfound) {
console.log(duplicateemailfound);
return res.status(400).json({
success: false,
message: "Email already registered",
});
} else {
let userRole = req.body.role.toLowerCase();
console.log("userRole:", userRole);
sequelize.models.Role.findOne({
where: {
role_name: userRole,
},
})
.then((foundRole) => {
// console.log(foundRole);
if (foundRole == null) {
return res.status(400).json({
success: false,
role: "null or not found",
});
}
// console.log("foundRole", foundRole); // .role_id
let addUser = {
email: req.body.email,
password: req.body.password,
name: req.body.name,
phone: req.body.phone,
role_id: foundRole.role_id,
};
sequelize.models.User.create(addUser, {
include: [{ model: sequelize.models.UserRoles }],
})
.then((newUser) => {
console.log("new user", newUser);
return res.status(201).json({
success: true,
newuser: newUser,
});
})
.catch((error) => {
console.log(error);
res.status(400).json({
success: false,
// message: "Duplicate Email was Found",
error: error.errors[0].message,
error: error,
});
});
})
.catch((error) => {
console.log(error);
res.status(400).json({
error: error,
msg: "bbb",
});
});
}
})
.catch((err) => {
console.log(err);
});
}
};
You create some a class for each Model and extend them with Model class of sequelize, this if fine.
Now, you define a static method inside the class named associate(model) where you define the rule for that class. This is fine because you used static which is required here to since it will be a property of the class, not of an object.
Then you call the initialize method (a in-built method of class Model). In the same way you need to call your defined associate.
Here is a problem, because in the structure that you have now, you can't call that method in it's own class file, becuase you need the other Model to pass it as parameter.
So there are 2 solutions:
Import your User model inside Role model file and call the static method, like this:
const User = require('User')
class Role extends Model {
static associate(model) {
Role.belongsToMany(model, {
foreignKey: "role_id",
through:'UserRoles',
as:"roles"
});
}
}
Role.init(
{
role_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
unique:true
},
role_name: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
role_desc: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: "Role",
}
);
Role.associate(User);
This will use your User model to pass it to the static method and finally to run the belongsToMany
Second solution would be to create an index file for your Models, where you import both of them and you can simply run that belongsToMany there, and then simply import that index file in the main file of your application, something like this:
User.js
const index = require('./../index.js');
const Sequelize = require('sequelize');
const Model = Sequelize.Model;
const sequelize = index.sequelize;
class User extends Model{}
User.init({
username: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
password: {
type: Sequelize.STRING,
allowNull: false
},
role: {
type: Sequelize.STRING,
allowNull: false
}
},{
sequelize,
modelName: 'user'
});
module.exports = {
User: User
}
Role.js will look the same but with your own model.
and index.js would look like this:
const Sequelize = require('sequelize');
const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USERNAME, process.env.DB_PASSWORD, {
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT
});
exports.sequelize = sequelize;
const user = require('./models/User');
const role= require('./models/Role');
role.belongsToMany(user, {
foreignKey: "role_id",
through:'UserRoles',
as:"roles"
});
sequelize.sync(user);
sequelize.sync(role);
exports.db = {
user: user,
role: role
}
I'm learning databases, trying to create a user and his two-factor authentication codes. Where one User can have multiple TwoFa
And so, there are 2 tables, User and TwoFa
user.ts
export interface IUser {
id: string;
email: string;
password: string;
twoFa: boolean; // Это флаг, включена ли двухфакторка
}
export interface IUserInstance
extends Model<IUser, Omit<IUser, "id" | "twoFa">>,
IUser,
IDateAt {}
export const User = sequelize.define<IUserInstance>(
"User",
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
twoFa: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
}
);
twoFa.ts
interface ITwoFa {
id: string;
ua: string;
}
export interface ITwoFaInstance
extends Model<ITwoFa, Omit<ITwoFa, "id">>,
ITwoFa {}
export const TwoFa = sequelize.define<ITwoFaInstance>(
"TwoFa",
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
ua: {
type: DataTypes.STRING,
allowNull: false,
}
}
);
Also a file with associations
User.hasMany(TwoFa, {
as: "twoFaCodes",
onDelete: "CASCADE",
onUpdate: "CASCADE",
});
TwoFa.belongsTo(User);
await User.sync({ alter: isDev });
await TwoFa.sync({ alter: isDev });
Below is a test script for creating User and TwoFa
const user = awaitUser.create({
login: "someLogin",
email: "someemail#gmail.com",
password: await argon2.hash("sfdsfs"),
});
const twoFaCode = await TwoFa.create(
{
ua: "dsfjdskfsd",
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
// #ts-ignore
User: user // тут ругается тайпскрипт
},
{
include: [User],
},
);
В итоге получаю ошибку
ValidationErrorItem {
message: 'id must be unique',
type: 'unique violation',
path: 'id',
value: '14218bdb-5fef-4777-bdfd-094551d09ec5',
origin: 'DB',
instance: [User],
validatorKey: 'not_unique',
validatorName: null,
validatorArgs: []
}
Actually I have 2 questions now:
What did I do wrong in associations?
How to create the correct type to create TwoFa so that there is no typescript error and the key is not User but user
Thanks!
UPD
if i add in associations foreignKey: "userId", and when creating TwoFa userId: user.id, then everything will work.
Now the question is, why didn’t it work with include?
You're trying to create a user associated with a new TwoFa instance that has the same primary key value.
If you indicate include in create that means you want to create a user along with a TwoFa record and that's not what you want to get.
If you just want to create a TwoFa record and associate it with an existing user then just indicate UserId without include option:
const twoFaCode = await TwoFa.create(
{
ua: "dsfjdskfsd",
UserId: user.id
}
);
By default you will have a foreign key field name as ModelName+Id (UserId) if you don't indicate foreginKey option explicitly in associations
Bug Description
after running sequelize sync with options alter: true, all foreign keys in tables set to null.
await sequelize.sync({ alter: true }).then(async (result) => {
await initDataUserGroup();
await initDataUserAdmin();
await initDataGateType();
});
and here my one model which has foreign key :
const { DataTypes } = require("sequelize");
const { sequelize } = require("../services/DatabaseService");
const UserGroup = require("./UserGroup");
const Biodata = require("./Biodata");
const User = sequelize.define("user", {
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
notNull: {
msg: "Username must not be empty",
},
notEmpty: true,
},
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
notNull: true,
notEmpty: true,
isEmail: true,
},
},
password: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: true,
notEmpty: true,
},
},
});
User.UserGroup = User.belongsTo(UserGroup);
User.Biodata = User.hasOne(Biodata, {
foreignKey: "userId",
as: "biodata",
});
module.exports = User;
What do you expect to happen?
It should not set all foreign keys to null after running sync with options { alter: true }
What is actually happening?
userGroupId in table user is null and userId in table biodata is also null.
Environment
Sequelize version: ^6.6.5
Node.js version: 12.9.1
Dialect: SQLite (SQLite3)
From my issue : https://github.com/sequelize/sequelize/issues/13464
anyone can help me please?
after struggling for a couple of days, I managed to resolve it by disabling and re-enabling foreign keys before and after syncing :
await sequelize.query("PRAGMA foreign_keys = false;");
await sequelize
.sync({ alter: true })
.then(async (result) => {
await initDataUserGroup();
await initDataGateType();
})
.catch((errorMessage) => console.error("sync error = ", errorMessage));
await sequelize.query("PRAGMA foreign_keys = true;");
Feel free to inform me a better solution than the current one.
I am trying to set up sequelize in my node project and for now I have
//sequelize init
const { DataTypes } = Sequelize;
const sequelize = new Sequelize({
database: database,
username: user,
host: server,
password: password,
dialect: 'mssql',
dialectOptions: {
options: {
useUTC: true,
dateFirst: 1,
}
},
define:{
timestamps:false,
paranoid:false,
freezeTableName: true
}
});
//and my Model
const User= sequelize.define('User', {
// attributes
id: {
field:'Id',
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true
} ,
startTime: {
field:'startTime',
type: Sequelize.DATE
}
});
I try to setup version:true to enable Optimistic Locking
I put it in model
const Vessel = sequelize.define('FDMData', {
// attributes
id: {
field:'vesselId',
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true
} ,
startTime: {
field:'startTime',
type: Sequelize.DATE
}
},{
version:true
}
);
and I get Unhandled rejection SequelizeDatabaseError: Invalid column name 'version'.
I also tried to set it as global while init
const { DataTypes } = Sequelize;
const sequelize = new Sequelize({
database: database,
username: user,
host: server,
password: password,
dialect: 'mssql',
dialectOptions: {
options: {
useUTC: true,
dateFirst: 1,
}
},
define:{
timestamps:false,
paranoid:false,
freezeTableName: true,
version: true
}
});
and again, I get Unhandled rejection SequelizeDatabaseError: Invalid column name 'version'.
What am I missing? How can I fix this?
Thanks
When you set version: true and you are creating your database structure manually, sequelize expect to find a column
named version on the table : so add a column version INTEGER NOT NULL DEFAULT 0 to your tables.
You can also name the versioning column what ever you want, just passe a string version: "myVersionColumn"
If you let sequelize handle the creation of the DB structure, it generate a DDL for the FDMData table that look like
CREATE TABLE IF NOT EXISTS FDMData (
vesselId INTEGER NOT NULL ,
startTime DATETIME,
version INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (vesselId)
)
and your code work juste fine. For example
Vessel.sync().then(model=> {
// or sequelize.sync()
console.log(model)
}).catch(error=> {
console.log(error)
})
you are almost there, based on the docs, setting version to true or set it to whatever name you want do the trick
Enable optimistic locking. When enabled, sequelize will add a version count attribute
to the model and throw an OptimisticLockingError error when stale instances are saved.
Set to true or a string with the attribute name you want to use to enable.
however, just in the next section to optimistic locking -Database synchronization- it says
When starting a new project you won't have a database structure and using Sequelize you won't need to
meaning, sequelize doesn't depend on a sql structure already set in your database for this purpose if you sync your models after you define them except for the database definition, it will automatically create it for you including the version field, here is an example
const Sequelize = require('sequelize');
const config = {
username: "root",
password: "123",
tableName: "test",
options: {
host: '127.0.0.1',
dialect: 'mysql',
pool: {
max: 3,
min: 1,
acquire: 30000,
idle: 10000
},
define: {
timestamps:false,
paranoid:false,
freezeTableName: true,
version: true
}
}
};
const sequelize = new Sequelize(config.tableName, config.username, config.password, config.options);
//and my Model
const User= sequelize.define('User', {
// attributes
id: {
field:'Id',
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true
} ,
startTime: {
field:'startTime',
type: Sequelize.DATE
}
});
User.sync();
if you run this script you will see the following sql statements executed
Executing (default): CREATE TABLE IF NOT EXISTS User (Id INTEGER
NOT NULL , startTime DATETIME, version INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (Id)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM User
however, if you don't want sequelize to sync your models, you have to explicitly have that field in your already established tables, but not explicitly defined in sequelize model as it will automatically know its there.
Adding an example, a User model (using sequelize v6):
const { Sequelize, Model, DataTypes, Deferrable, DatabaseError } = require('sequelize');
const sequelize = require('../lib/db.sequelize').db;
class User extends Model {
}
User.init({
// The following specification of the 'id' attribute could be omitted
// since it is the default.
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
version: { // Optimistic Locking
allowNull: false,
type: DataTypes.INTEGER,
defaultValue: 0
},
name: {
allowNull: false,
type: DataTypes.STRING,
},
email: {
allowNull: false,
type: DataTypes.STRING,
unique: true,
validate: {
isEmail: true
}
}
}, {
sequelize,
modelName: 'user',
version: true, // Optimistic Locking
indexes: [
{
name: 'user_id_index',
method: 'BTREE',
fields: ['id'],
},
{
name: 'user_email_index',
method: 'BTREE',
fields: ['email'],
}
],
freezeTableName: true
});
module.exports = User;
Note that a version attribute was added to the model as well as passing the "version: true" to the options.