Correct way of doing relationships in sequelize model - node.js

Recently we moved to Sequelize ORM from Knex due to which we have existing database to which models are map to . Everything working fine except model relationships.
So I have a users table and roles table. Each user can have one role but each role can be assigned to many users. To map these to models, I created model files that is as follows.
User Model:
const Sequelize = require('sequelize');
const sequelize = require('../utility/dbConnection');
const roles = require('./Roles');
module.exports = sequelize.define("User", {
id: {
type: Sequelize.INTEGER(11),
primaryKey: true,
autoIncrement: true,
allowNull: false
},
firstName: Sequelize.STRING(255),
middleName: Sequelize.STRING(255),
lastName: Sequelize.STRING(255),
email: {
type: Sequelize.STRING(255),
unique: true
},
phoneNumber: {
type: Sequelize.STRING(255),
unique: true
},
is_phonenumber_verified: {
type: Sequelize.BOOLEAN,
default: false
},
is_email_verified: {
type: Sequelize.BOOLEAN,
default: false
},
roleID: {
type: Sequelize.INTEGER
},
password: Sequelize.STRING(255)
// }, {
// defaultScope: {
// attributes: { exclude: ['password'] }
// }
}, {
tableName: 'users'
},{
classMethods: {
associate: function() {
this.hasOne(roles,{foreignKey: 'roleID'})
}
}
}
);
roles Model:
const Sequelize = require('sequelize');
const sequelize = require('../utility/dbConnection');
module.exports = sequelize.define("Role", {
id: {
type: Sequelize.INTEGER(11),
primaryKey: true,
autoIncrement: true,
allowNull: false
},
name: {
type: Sequelize.STRING(255),
unique: true
},
},{
tableName: 'roles'
});
Now in the controller file, I want to fetch user details and role name for which code snippet are as follows
const userModel = require('../../models/Users');
const rolesModel = require('../../models/Roles');
let user = await userModel.findOne({ where: { email: req.body.email } },{include:[{model:rolesModel}]});
The problem is it only fetches rolesID from the Users table but not roles name from roles table .
Can anyone help me on this what I am doing wrong here ?

You have To Write Your Include Condition Like This
const userModel = require('../../models/Users');
const rolesModel = require('../../models/Roles');
let user = await userModel.findOne({
where: {
email: req.body.email
},include:
[{ model: rolesModel }]
});
you can use belongsTo relation instead of using hasOne
add this relation in roles table and remove from users table
classMethods: {
associate: function() {
this.belongsTo(users,{foreignKey: 'roleID'})
}
}
You can see this Sequelize Associations docs to know more about Associations and relations

Related

how to write migrations to add foreignkey to already existing tables in sequelize

I have this already created two tables called User and Profile.
This is how my model for User looks like..
const Sequelize = require("sequelize");
const db = require("../db");
const User = db.define("User", {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: {
type: Sequelize.STRING,
allowNull: true,
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
validator: {
isEmail: true,
},
},
});
module.exports = User;
and model for Profile looks like..
const Sequelize = require("sequelize");
const User = require("./User");
const db = require("../db");
const Profile = db.define("Profile", {
image: {
type: Sequelize.STRING,
},
description: {
type: Sequelize.TEXT,
},
});
module.exports = Profile;
Now I want to define a one-to-one relationship between User and Profile such that user will recieve a profileId column.
so i am defining it like this
Profile.hasOne(User, {
foreignKey: {
allowNull: false,
},
});
User.belongsTo(Profile);
Now i am not able to figure out how to write migrations for the newly added foreign key
can anyone help me please..
Thanks.
I got the answer. for someone who is confused like me here is the answer
since the User table already exists, migrations for the foreignkey will look like this
module.exports = {
async up(queryInterface, Sequelize) {
return await queryInterface.addColumn("Users", "ProfileId", {
type: Sequelize.INTEGER,
references: {
model: "Profiles",
key: "id",
},
});
},
async down(queryInterface, Sequelize) {
return await queryInterface.removeColumn("Users", "ProfileId", {
type: Sequelize.INTEGER,
references: {
model: "Profiles",
key: "id",
},
});
},
};
the Users in addColumn and removeColumn is the name of the table in which foreignkey was added.
the ProfileId is the name for foreignkey which you would have specified in hasOne.
hope this helps..

one-to-many relationship with sequelize

I have 2 tables, users and users_signature where the signature takes several applications and I need to make a select according to the application.
Models:
user
const { INTEGER } = require('sequelize');
const Sequelize = require('sequelize');
const database = require('../../config/db');
const User_has_signature = require('./user_has_signature');
const Usuario = database.define('usuario', {
usu_id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
usu_rg: {
type: Sequelize.STRING,
},
},
{
freezeTableName: true,
createdAt: false,
updatedAt: false,
});
User.hasMany(User_has_signature, {as: 'user_has_signature'});
module.exports = User;
User_has_signature
const { INTEGER } = require('sequelize');
const Sequelize = require('sequelize');
const database = require('../../config/db');
const User_has_signature = database.define('user_has_signature', {
usu_has_signature_id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
user_usu_id: {
type: Sequelize.STRING,
},
signature_aplicativo_signature_aplicativo_id: {
type: Sequelize.STRING,
},
signature_type_signature_type_id: {
type: Sequelize.STRING,
},
},
{
freezeTableName: true,
createdAt: false,
updatedAt: false,
});
User_has_signature.associate = (models) => {
User_has_signature.belongsTo(models.User,
{ foreignKey: 'user_usu_id', as: 'users' });
};
module.exports = User_has_signature;
Controller
UserController
const User = require("../../model/user/user")
const User_has_signature = require("../../model/user/user_has_signature")
async index(req, res){
const user = await User.findAll({
include: [{
model: User_has_signature,
foreignKey: 'user_usu_id',
through: {
where: {signature_ttype_signature_type_id: 3}
}
}]
})
res.status(200).json(user)
return
}
The error that is returning to me in the terminal is: (node:15168)
UnhandledPromiseRejectionWarning: SequelizeEagerLoadingError:
user_has_signature is associated to usuario using an alias. You must
use the 'as' keyword to specify the alias within your include
statement
I think you have to specify the alias you have given when writing your query :
include: [{
model: User_has_signature,
foreignKey: 'user_usu_id',
as : 'users'
through: {
where: {signature_ttype_signature_type_id: 3}
}]
Either way I'm using Sequelize more in Typescript, so I'm not sure of the syntax.
The way it handles One to Many relationship isn't the clearest I've seen (Like Symfony or Spring)

How to make sequelize model seperate file but same sequelize connection

everyone. I'm very new for sequelize ORM with nodejs.
I've just created couple files for models seperately and I also seperate sequelize.js to connect to database.
The problem is when I make association between model(file)(Self Associate work well) and I've got an error
**** "hasMany called with something that's not a subclass of Sequelize.Model"
I tried to solved this but doesn't work until I put every model in the same file. So, I realized that every model must use common sequelize connection.
Is there anyways to solve this problem without sequelize-cli (I don't want to use sequelize-cli)
My code belows
Many thanks,
sequelize.js
const config = require("config");
const { Sequelize } = require("sequelize");
const sequelize = new Sequelize(
config.get("database"),
config.get("user"),
config.get("cipher"),
{
dialect: "mariadb",
timezone: "Asia/Bangkok",
}
);
module.exports = sequelize;
user.js
const { DataTypes, Model } = require("sequelize");
const sequelize = require("./sequelize");
const Position = require("./position");
class User extends Model {}
User.init(
{
uuid: {
type: DataTypes.UUID,
allowNull: false,
defaultValue: DataTypes.UUIDV4,
},
title: {
type: DataTypes.STRING,
allowNull: false,
},
firstname: {
type: DataTypes.STRING,
allowNull: false,
},
lastname: {
type: DataTypes.STRING,
allowNull: false,
},
phone: {
type: DataTypes.STRING,
allowNull: true,
},
email: {
type: DataTypes.STRING,
allowNull: true,
validate: {
isEmail: true,
},
},
imgurl: {
type: DataTypes.STRING,
allowNull: true,
},
login: {
type: DataTypes.STRING,
allowNull: false,
},
passphase: {
type: DataTypes.STRING,
allowNull: false,
},
status: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1,
},
},
{
sequelize,
modelName: "User",
tableName: "users",
timestamps: true,
}
);
User.belongsToMany(User, {
as: "CreatedUser",
foreignKey: "user_id",
through: "UserCreator",
});
User.belongsToMany(User, {
as: "Creator",
foreignKey: "creator_id",
through: "UserCreator",
});
User.belongsToMany(User, {
as: "ModifiedUser",
foreignKey: "user_id",
through: "UserModifier",
});
User.belongsToMany(User, {
as: "Modifier",
foreignKey: "modifier_id",
through: "UserModifier",
});
User.belongsTo(Position);
module.exports = User;
position.js
const { DataTypes, Model } = require("sequelize");
const sequelize = require("./sequelize");
const User = require("./user");
class Position extends Model {}
Position.init(
{
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
status: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1,
},
},
{
sequelize,
modelName: "Position",
tableName: "postions",
timestamps: true,
}
);
Position.hasMany(User, {
foreignKey: {
type: DataTypes.UUIDV4,
allowNull: false,
},
});
module.exports = Position;
Sequelize has a .define() method to create all your schemas in JSON. If you want to keep your connection to the database separate from your models, I suggest doing something like this
./models/schemas/User.js
const userSchema = {
attribute1: {
type: ...,
option1: ...,
option2: ...,
.
.
},
.
.
}
export default userSchema
Do the same with Position.js
Then:
./models/models.js
import {sequelize} from 'path/to/sequelize.js'
import positionSchema from './schemas/Position.js'
import userSchema from './schemas/User.js'
const User = sequelize.define("user", userSchema, { freezeTableName: true });
const Position = sequelize.define("position", positionSchema, { freezeTableName: true });
// add associations
export {
User,
Position,
.
.
}

How to define an association to a model with array of foreign keys using sequelize with postgres

I have two models using mongoose as shown below:
Teams
var mongoose = require('mongoose');
var teamsSchema = new mongoose.Schema({
Name: {
type: String,
required: true,
unique: true
},
Description: {
type: String
},
Is_Active: {
type: String,
enum: ["Y", "N"],
default: 'Y'
}
}, { timestamps: true });
module.exports = mongoose.model('teams', teamsSchema);
Users
var mongoose = require('mongoose');
var usersSchema = new mongoose.Schema({
First_Name: {
type: String
},
Last_Name: {
type: String
},
Email: {
type: String,
required: true,
unique: true
},
Teams: [{ type: mongoose.Schema.Types.ObjectId, ref: 'teams' }],
Is_Active: {
type: String,
enum: ["Y", "N"],
default: 'Y'
}
}, { timestamps: true });
module.exports = mongoose.model('users', usersSchema);
We would like to migrate from MongoDB to PostgreSQL and we are using sequelize and created models as below
Teams
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class teams extends Model {
static associate(models) {
// define association here
}
};
teams.init({
Name: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
Description: DataTypes.STRING,
Is_Active: {
type: DataTypes.ENUM,
values: ['Y', 'N'],
defaultValue: 'Y'
}
}, {
sequelize,
modelName: 'teams',
});
return teams;
};
Users
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class users extends Model {
static associate(models) {
// define association here
}
};
users.init({
First_Name: DataTypes.STRING,
Last_Name: DataTypes.STRING,
Email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
Teams: {
type: DataTypes.ARRAY(DataTypes.INTEGER)
},
Is_Active: {
type: DataTypes.ENUM,
values: ['Y', 'N'],
defaultValue: 'Y'
}
}, {
sequelize,
modelName: 'users',
});
return users;
};
With mongoose, we could able to get the User details along with Teams and Teams along with Users. How to associate a model in sequelize with an array of keys? How to write queries(find/join) in Sequelize with an array of foreign keys.
Once this is resolved, we need to think of nested arrays.
Defining associations between models are very simple, as you can find it here.
I can see that you already have the associate method in each model.
You should add this in each method in order to have the correct associations:
Teams
this.belongsToMany(models.users, { through: 'TeamUsers' });
Users
this.belongsToMany(models.teams, { through: 'TeamUsers' });
Also, you don't need the Teams property in the Users model, since many-to-many associations exploit an external table in relational databases.
Here is an example of how to include users when querying for teams:
models.teams.find({
include: models.users
}).then(foundTeamsWithUsers => {
// Handle here your teams: use users to access the list of users related to each team
}).catch(handleErrorFunction);
You can find more examples about eager loading with sequelize.

Problem setting up Sequelize association - query with 'include' is failing

I'm new to Sequelize and trying to test if an n:m association I set up between two models, User and Podcast, is working. When I try to run this query, I get some kind of DB error that isn't specific about what's wrong:
User.findOne({
where: { id: id },
include: [{ model: Podcast }]
});
Does anyone know what I'm messing up? I suspect there's something wrong in how I've set up the association, like I'm referencing the names of tables slightly incorrectly, but the migration to create the association worked.
Here's my User.js model file:
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
photo: {
type: DataTypes.STRING
}
});
User.associate = function(models) {
// associations can be defined here
User.belongsToMany(models.Podcast, {
through: 'user_podcast'
});
};
return User;
};
And here's my Podcast.js file:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Podcast = sequelize.define('Podcast', {
id: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false
},
title: {
type: DataTypes.STRING,
allowNull: false
},
thumbnail: {
type: DataTypes.STRING
},
website: {
type: DataTypes.STRING
}
});
Podcast.associate = function(models) {
// associations can be defined here
Podcast.belongsToMany(models.User, {
through: 'user_podcast'
});
};
return Podcast;
};
And here's the migration I ran to join the two tables:
'use strict';
module.exports = {
up: function(queryInterface, Sequelize) {
return queryInterface.createTable('user_podcast', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
userId: {
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id'
}
},
podcastId: {
type: Sequelize.STRING,
references: {
model: 'Podcasts',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: function(queryInterface, Sequelize) {
return queryInterface.dropTable('user_podcast');
}
};
And here's the project on Github for further reference:
https://github.com/olliebeannn/chatterpod
You don't need to create a migration for the M:N table. Now you have something wrong on your user_podcast model. If you are setting a M:N relation between to tables your primary key will be the combination between the foreign key from these two models. If you still want a single id primary key for your table, then you won't use belongsToMany instead use hasMany on user and podcast models pointing to a new model user_podcast.
As far as I see on your first query, it seems that you really need a M:N relation so you can define the model as you do with user and podcast like this:
module.exports = (sequelize, DataTypes) => {
const UserPodcast = sequelize.define('user_podcast', {
userId: {
// field: 'user_id', #Use 'field' attribute is you have to match a different format name on the db
type: DataTypes.INTEGER
},
podcastId: {
// field: 'podcast_id',
type: DataTypes.INTEGER
},
});
UserPodcast.associate = function(models) {
models.User.belongsToMany(models.Podcast, {
as: 'podcasts', //this is very important
through: { model: UserPodcast },
// foreignKey: 'user_id'
});
models.Podcast.belongsToMany(models.User, {
as: 'users',
through: { model: UserPodcast },
// foreignKey: 'podcast_id'
});
};
return UserPodcast;
};
I do prefer to have the belongsToMany associations on the save function where I define the join model, and you have to notice that I used as: attribute on the association. This is very important because this will help sequelize to know which association are you referring on the query.
User.findOne({
where: { id: id },
include: [{
model: Podcast,
as: 'podcasts' //here I use the previous alias
}]
});

Resources