Add default role to many-many relation in Sequelize.js - node.js

I have two Sequelize.js models what are connected by many-to-many relation.
User:
module.exports = (sequelize, DataTypes) => {
const model = sequelize.define('User', {
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
password: {
type: DataTypes.CHAR,
length: 60,
allowNull: false
}
}
});
model.associate = models => {
model.belongsToMany(models.Role, {
hooks: true,
through: 'user_roles'
})
};
return model;
};
Role:
module.exports = (sequelize, DataTypes) => {
const model = sequelize.define('Role',
{
name: {
type: DataTypes.STRING,
unique: false,
allowNull: false
}
}
);
model.associate = models => {
model.belongsToMany(models.User, {
hooks: true,
through: 'user_roles'
});
};
return model;
};
Table user_roles is created automatically.
How to set default role what will be inserted into user_roles while creating new User? Is there way to define it in models definition or I am just supposed to create User new user and then create relation in one transaction?

First you need to create a model for the user_roles after creating that you have to set a hook inside your user model that will automatically insert roles in to your user_roles table.
This is how you can do it.
For Example :
User Model
const { hooks } = require('./user-role.hook');
module.exports = (sequelize, DataTypes) => {
const model = sequelize.define('User', {
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
password: {
type: DataTypes.CHAR,
length: 60,
allowNull: false
}
}, { hooks });
model.associate = models => {
model.belongsToMany(models.Role, {
hooks: true,
through: 'user_roles'
})
};
return model;
};
Hook file
exports.hooks = {
afterCreate: (User, payload) => {
AddUserRole(User, payload);
}
}
function AddUserRole(User, payload) {
let InsertArr = {
user_id: User._id,
role_id: 1 // pass default role id
}
model.UserRole.create(InsertArr);
}

Related

Many to many association not returning associated table

I'm trying to add 'roles' to users - each user can have multiple roles, and roles aren't limited to a number of users - they can be assigned to as many users as is needed. I followed a few tutorials, read through quite a few stackoverflow questions/answers, and still can't seem to get this to work.
user.model.js
'use strict';
const { Model } = require('sequelize');
const PROTECTED_ATTRIBUTES = ['password'];
module.exports = (sequelize, DataTypes) => {
class User extends Model {
toJSON() {
// hide protected fields
const attributes = { ...this.get() };
// eslint-disable-next-line no-restricted-syntax
for (const a of PROTECTED_ATTRIBUTES) {
delete attributes[a];
}
return attributes;
}
static associate(models) {
User.belongsToMany(models.Role, {
through: 'UserRoles',
as: 'roles',
foreignKey: 'roleId'
});
}
}
User.init({
name: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
lastLoginAt: {
type: DataTypes.DATE,
allowNull: true
},
lastIPAddress: {
type: DataTypes.STRING,
allowNull: true
}
}, {
sequelize,
modelName: 'User',
paranoid: true,
tableName: 'users'
});
return User;
};
role.model.js
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Role extends Model {
static associate(models) {
Role.belongsToMany(models.User, {
through: 'UserRoles',
as: 'users',
foreignKey: 'userId'
});
}
};
Role.init({
roleName: DataTypes.STRING
}, {
sequelize,
modelName: 'Role',
});
return Role;
};
userroles.model.js
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class UserRoles extends Model {
static associate(models) {
// define association here
}
};
UserRoles.init({
userId: DataTypes.INTEGER,
roleId: DataTypes.INTEGER
}, {
sequelize,
modelName: 'UserRoles',
});
return UserRoles;
};
user.controller.js
async function getUsers(req, res, next) {
const name = req.query.name;
const condition = name ? { name: { [Op.like]: `%${name}%` } } : null;
User.findAll({ where: condition, include: { model: Role, as: 'roles'} })
.then(data => {
res.send(data);
})
.catch(err => {
const error = new createError(500, 'Some error occurred while retrieving users.');
return next(error);
});
}
The query returns a 'roles' array, but nothing is inside it even though the UserRoles table has an entry assigned to an existing user and existing role.
The query it generates, in case that helps:
SELECT "User"."id", "User"."name", "User"."email", "User"."password", "User"."lastLoginAt", "User"."lastIPAddress", "User"."createdAt", "User"."updatedAt", "User"."deletedAt", "roles"."id" AS "roles.id", "rol
es"."roleName" AS "roles.roleName", "roles"."createdAt" AS "roles.createdAt", "roles"."updatedAt" AS "roles.updatedAt", "roles->UserRoles"."userId" AS "roles.UserRoles.userId", "roles->UserRoles"."roleId" AS "roles.UserRoles.role
Id", "roles->UserRoles"."createdAt" AS "roles.UserRoles.createdAt", "roles->UserRoles"."updatedAt" AS "roles.UserRoles.updatedAt" FROM "users" AS "User" LEFT OUTER JOIN ( "UserRoles" AS "roles->UserRoles" INNER JOIN "role" AS "ro
les" ON "roles"."id" = "roles->UserRoles"."roleId") ON "User"."id" = "roles->UserRoles"."roleId" WHERE ("User"."deletedAt" IS NULL);
role.model.js
'use strict';
const {Model} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Role extends Model {
static associate(models) {
//...
Role.belongsToMany(models.UserRoles, {
as: 'userRoles',
foreignKey: 'roleId',
});
}
}
// ...
return Role;
};
User.findAll({where: condition, include: {model: Role, as: 'roles', include: ['userRoles']}})
^^^^^^^^^^^^^^^^^^^^^^

Sequelize (PostgresSQL) many to many chat implementation

I've been trying to create a chat app with Node JS and Sequelize. Now i'm stuck at a problem of creating a query to find a conversation that has my id and user's id(the one i'm trying to text). So the thing i'm trying to do is send a post request with and id of a user i'm sending a message to, then i look through my Conversation model and check if that conversation has my id and id of the user i'm texting to.
My models are associated through Many to Many relationship. So the main objective is to find a conversation with only my ID and ID of the user i'm texting to with the same ConversationId.
Here are my models:
User
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
"User",
{
name: { type: DataTypes.STRING },
password: { type: DataTypes.STRING, allowNull: false },
username: { type: DataTypes.STRING, allowNull: false },
email: { type: DataTypes.STRING, allowNull: false },
},
{}
);
User.belongsToMany(models.Conversation, {
as: "conversations",
foreignKey: "user_id",
through: models.ConversationUsers,
});
User.hasMany(models.Message, {
as: "messages",
});
};
return User;
};
Conversation
module.exports = (sequelize, DataTypes) => {
const Conversation = sequelize.define(
"Conversation",
{
lastMessage: DataTypes.STRING,
recipients: DataTypes.ARRAY(DataTypes.INTEGER),
},
{
sequelize,
modelName: "Conversation",
}
);
Conversation.associate = (models) => {
Conversation.belongsToMany(models.User, {
as: "participants",
foreignKey: "conversation_id",
through: models.ConversationUsers,
});
Conversation.hasMany(models.Message, {
as: "messages",
});
};
return Conversation;
};
ConversationUsers Many to Many through model
"use strict";
module.exports = (sequelize, DataTypes) => {
const ConversationUsers = sequelize.define(
"ConversationUsers",
{
user_id: DataTypes.INTEGER,
conversation_id: DataTypes.INTEGER,
},
{
sequelize,
modelName: "ConversationUsers",
}
);
return ConversationUsers;
};
Message
module.exports = (sequelize, DataTypes) => {
const Message = sequelize.define(
"Message",
{
conversationId: { type: DataTypes.INTEGER, allowNull: false },
sentTo: DataTypes.INTEGER,
sentFrom: DataTypes.INTEGER,
body: { type: DataTypes.STRING, allowNull: false },
},
{
sequelize,
modelName: "Message",
}
);
Message.associate = (models) => {
Message.belongsTo(models.User, {
as: "messageTo",
foreignKey: "sentTo",
});
Message.belongsTo(models.User, {
as: "messageFrom",
foreignKey: "sentFrom",
});
Message.belongsTo(models.Conversation, {
as: "messages",
});
};
return Message;
};
I think you can remove some pieces from your models and rework it a bit.
Messages don't need a sentTo, they only need a sentFrom. You can use the ConversationUsers table to know who the recipients are. This also gives you the flexibility to have Conversations with more than 2 members, because your current model essentially enforces that a Message can only be to one user.
So let's walk through the models first with changes
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
"User",
{
name: { type: DataTypes.STRING },
password: { type: DataTypes.STRING, allowNull: false },
username: { type: DataTypes.STRING, allowNull: false },
email: { type: DataTypes.STRING, allowNull: false },
},
{
// I think moving the associations to other files might make this more clear
}
);
};
return User;
};
module.exports = (sequelize, DataTypes) => {
const Conversation = sequelize.define(
"Conversation",
{
// perhaps something like a subject could go here e.g.
subject: DataTypes.STRING(500),
},
{
sequelize,
modelName: "Conversation",
}
);
Conversation.associate = (models) => {
Conversation.hasMany(models.Message, {
as: "ConversationMessages",
}); // adds ConversationId onto Message, gives us Conversation.getConversationMessages() etc
models.Message.belongsTo(Conversation); // create association both ways for convenience methods to find convo from a message
models.Message.hasOne(Conversation, {
as: 'LastMessage',
constraints: false,
allowNull:true,
defaultValue:null
}); // adds LastMessageId onto Conversation model (you'll have to write code to maintain this value, probably through an afterCreate hook on Message model)
};
return Conversation;
};
module.exports = (sequelize, DataTypes) => {
const Message = sequelize.define(
"Message",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true, // if you want to do the hook thing i talked about to set LastMessageId, you need to put this in
},
body: { type: DataTypes.STRING, allowNull: false },
},
{
sequelize,
modelName: "Message",
}
);
Message.associate = (models) => {
Message.belongsTo(models.User, {as: "sentFromUser"});
};
return Message;
};
// I'm going to rename your many-to-many table "ConversationMembers"
module.exports = (sequelize, DataTypes) => {
const ConversationMembers = sequelize.define(
"ConversationMembers",
{
// again, the associations will build these fields for you
},
{
sequelize,
modelName: "ConversationMembers",
}
);
models.Conversation.belongsToMany(models.User, {
through: "ConversationMember",
as: "Members",
}); // gives us Conversation.getMembers()
models.User.belongsToMany(models.Conversation, {
through: "ConversationMember",
as: "MemberConversations",
}); // gives us User.getMemberConversations()
ConversationMember.belongsTo(models.Message, { as: "LastReadMessage" }); // gives us the potential ability to track the last read message for each convo member as ConversationMember.LastReadMessageId, you'll need to set this value manually on read for each user if you care about having it
models.Conversation.hasMany(ConversationMember);
models.User.hasMany(ConversationMember);
return ConversationMember;
Okay now onto your question, which perhaps becomes simpler at this point. If you already know the ConversationId, all you need to do is check that the person who is sending the message is a member of the conversation. Then if they are, write a row into the Messages table. It doesn't matter who the message is "to"--you're writing to the members of the Conversation, not to any individual.
async function canMessageHelper({conversationId, userId }) {
const convo = await models.Conversation.findOne({
attributes: ["id"], // whatever attrs you need, probably not many if any
where: {
id: conversationId,
},
include: [{
model: models.ConversationMember,
attributes: ["ConversationId"], // whatever you need if anything
where: { // this where is critical, it creates an inner join so convo only returns if myUserId is a member of the Conversation
UserId: userId
}
}]
});
if (!convo) {
return false;
}
return convo;
}
async function sendMessage({conversationId, authorUserId, messageText}) {
const allowMessage = await canMessageHelper({conversationId, userId: authorUserId});
if (!allowMessage) {
return false;
}
await models.Message.create({sentFromUserId: authorUserId, body: messageText});
}
If you want to try this, be sure you remove any tables you've already created with these names from your database before you sync.
I have not provided any code for the hooks I mentioned, but you will have the groundwork to develop those ideas out.

How to keep sequelize associations name in lower_case?

Cannot get my foreignKey in lower_case, all my associations are PascalCase.
So I have a simple table "user" with role_id (foreignKey)
And I have table "role".
So when create a user it works fine. But when I query it search for foreignKey name like RoleId instead of role_id.
module.exports = (sequelize, DataTypes) => {
const Role = sequelize.define('Role', {
name: DataTypes.STRING
}, {
tableName: "role"
});
Role.associate = function (models) {
// associations can be defined here
Role.hasMany(models.User)
};
return Role;
}
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
autoIncrement: false,
},
email: DataTypes.STRING,
password: DataTypes.STRING,
role_id: DataTypes.INTEGER,
name: DataTypes.STRING,
MerchantId: DataTypes.STRING
}, {
tableName: 'user'
});
User.associate = function (models) {
User.belongsTo(models.Role)
User.belongsTo(models.Merchant)
};
return User;
};
Any solution to keep all name lower_case_underscore ?
I ran into a similar issue and had to set foreignKey on both sides of the relationship or it would automatically use RoleId.
Role.hasMany(models.User, {foreignKey: 'role_id'});
...
User.belongsTo(models.Role, {foreignKey: 'role_id'});
Another option is to set underscored: true in the global config. You would keep all your attribute names in camel case but the table names and field names would be in snake case.
config = {
"development": {
...
"define": {
"underscored": true
}
}
}
const sequelize = new Sequelize(config.database, config.username, config.password, config);
Try
User.belongsTo(models.Role, { foreignKey: 'role_id' ))

Sequelize associate not created

A very straightforward question. Am using nodejs 5.6, express 4 and sequelize 4. In the program, I have a user object which belongs to company object.
var User = sequelize.define('user', {
username: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
}
});
const Company= sequelize.define('companny', {
record: {
type: Sequelize.JSON
}
});
Company.associate = function(models) {
console.log(models)
Company.hasMany(models.User, {
foreignKey: {
// name: 'user',
allowNull: true
}
})
}
when running the program, the database is created with user and company table, but there is no foreign key in eight user or company. And the console outputs nothing. Any suggestion?
Finally, the index.js file here answers my question.
I would normally use this method to create my models:
// User model
export default (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
allowNull: false,
type: DataTypes.STRING,
unique: true
},
password: {
allowNull: false,
type: DataTypes.STRING,
}
});
return User;
};
// Company model
export default (sequelize, DataTypes) => {
const Company = sequelize.define('Company', {
record: {
type: DataTypes.JSON
}
});
Company.associate = (models) => {
console.log(models)
Company.hasMany(models.User, {
foreignKey: 'userId'
})
}
return Company;
};

How sequelize works?

I'm trying to understand how sequelize works on a simple example : User can have many posts and post can have only one user.
First question, I don't know if I have to use the migrations or the models with sync for creating my database. I mean, I have to put bearly the same code in both. This is the migration for the users table:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
username: {
allowNull: false,
type: Sequelize.STRING,
unique: true
},
password: {
allowNull: false,
type: Sequelize.STRING
},
email: {
allowNull: false,
type: Sequelize.STRING,
unique: true
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};
And this is the Post model :
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
username: DataTypes.STRING,
password: DataTypes.STRING,
email: DataTypes.STRING
}, {
classMethods: {
associate: (models) => {
User.hasMany(models.Post);
}
}
});
return User;
};
Do I also have to specify that the username, email can't be null and must be unique here in the model?
And how do I have to add the foreign key ? In one tutorial, they said me that the database add automaticly the foreign key but I don't think it works if I use the migrations, I have to set it manualy no?
For your version "sequelize": "^4.13.2":
classMethods and instanceMethods are removed.
Previous:
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 () {..}
See official docs Upgrade to V4
So for relations u should walkthrough this steps:
Import models
Call class "associate" method if exists
Export
Example:
// models/index.js
import fs from 'fs';
import path from 'path';
import Sequelize from 'sequelize';
import config from './config';
const sequelize = new Sequelize(config.db.url, config.db.options);
const DB = {};
// Import models
fs
.readdirSync(__dirname)
.filter(file => (file.indexOf('.') !== 0) && (file !== path.basename(__filename)) && (file.slice(-3) === '.js'))
.forEach((file) => {
const model = sequelize.import(path.join(__dirname, file));
DB[model.name] = model;
});
// Here u should call class method for associations
Object.keys(DB).forEach((modelName) => {
if ('associate' in DB[modelName]) {
DB[modelName].associate(DB);
}
});
DB.sequelize = sequelize;
DB.Sequelize = Sequelize;
export default DB;
All relations u can put in your models.
User:
// models/user.js
export default (sequelize, DataTypes) => {
const User = sequelize.define(
'users',
// Fields
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
// etc ...
},
// Options
{
timestamps: false, // <-- turn off timestamps
underscored: true, // <-- this option for naming with underscore. example: createdAt -> created_at
validate: {},
indexes: [],
},
);
User.associate = (models) => {
User.hasMany(models.post, {
// ...
});
User.hasMany(models.comment, {
// ...
});
// OR
models.user.hasMany(models.post, {
// ...
});
};
return User;
};
Post:
// models/post.js
export default (sequelize, DataTypes) => {
const Post = sequelize.define(
'posts',
// Fields
{
// ...
},
// Options
{
// ...
},
);
Post.associate = (models) => {
Post.belongsTo(models.user, {
// ...
});
// OR
models.post.belongsTo(models.user, {
// ...
});
};
return Post;
};
Do I also have to specify that the username, email can't be null and
must be unique here in the model?
Yes u should define all things in your models, such as keys, relations, whatever. Because your app use models for actions with database.
And how do I have to add the foreign key ? In one tutorial, they said
me that the database add automaticly the foreign key but I don't think
it works if I use the migrations, I have to set it manualy no?
Actually u cant define composite keys in migrations that creates the table and fields.
Best practise for migrations should be like this:
000000_create_users_table
000001_add_foreign_keys_to_users_table
000002_add_new_field_to_users_table
etc...
So u should add all things manually in migrations.
For adding indexes in migrations you should use queryInterface.addIndex
module.exports = {
up: queryInterface => queryInterface.addIndex(
'users',
{
unique: true,
fields: ['username', 'email'],
// if u want to rename u can use:
// name: 'whatever'
// by convention default name will be: table_field1_fieldN
},
),
down: queryInterface => queryInterface.removeIndex(
'users',
'users_username_email', // <-- this name by convention, but u can rename it
),
};
For "keys" you should use queryInterface.addConstraint
Primary Key
queryInterface.addConstraint('Users', ['username'], {
type: 'primary key',
name: 'custom_primary_constraint_name'
});
Foreign Key
queryInterface.addConstraint('Posts', ['username'], {
type: 'FOREIGN KEY',
name: 'custom_fkey_constraint_name',
references: { //Required field
table: 'target_table_name',
field: 'target_column_name'
},
onDelete: 'cascade',
onUpdate: 'cascade'
});
Check all API References
You are right you have to manually set the foreign key relations.
Here is official documentation link : http://docs.sequelizejs.com/manual/tutorial/associations.html
You can try following code:
var user_object = require('your_file_path');
var post_object = require('your_file_path');
user_object.hasMany(post_object, {
foreignKey: 'user_id',
sourceKey: 'user_id',
onDelete: 'cascade',
as:'Posts',
});
post_object.belongsTo(user_object, {
foreignKey: 'user_id',
sourceKey: 'user_id',
onDelete: 'cascade',
as:'Posts',
});
I am really just restrucuring your code.
// Create One database config file
var Sequelize=require('sequelize');
var connection=new Sequelize('project','user','password',{
dialect:'mysql',
logging:false
});
connection.authenticate()
.then(() => {
console.log("Connected to database");
})
.catch(err => {
//console.error("Can't connect to database :(\n", err);
});
module.exports={
database:connection,
}
//Your User Schema File
var database = require('your_file_path/DatabaseConnection').database;
var Sequelize = require('sequelize');
var Users = database.define('users', {
username: {
allowNull: false,
type: Sequelize.STRING,
unique: true
},
password: {
allowNull: false,
type: Sequelize.STRING
},
email: {
allowNull: false,
type: Sequelize.STRING,
unique: true
}
}, {
underscored: true
},hooks: {
beforeCreate: (user, option) => {
users.password = encrypto.encryptEntity(user.password);
//for automatic encryption of password
},
}
);
Users.sync();
//id, updated_at , and modified_at will be maintained by default
module.exports = {
Users
}
// your post file path
var Posts = database.define('posts', {
post_content: {
allowNull: false,
type: Sequelize.STRING,
unique: true
}
}, {
underscored: true
});
//importing User
var Users = require('file_path')
Users.hasMany(Posts, {
foreignKey: 'user_id',
sourceKey: 'user_id',
onDelete: 'cascade',
as:'Posts',
});
Posts.belongsTo(Users, {
foreignKey: 'user_id',
sourceKey: 'user_id',
onDelete: 'cascade',
as:'Users',
});
// two way binding.
Posts.sync();
BY maintaining Relation you can easily update data using setter and getter methods
Posts.setUsers(user_object);
// above code will automatically put the user_id found in user_object
//for select query you can use:
Users.findOne({
where:{
id:user_id
},
include: [{
model: Posts,
attributes: ['post_content'],
as: "Posts"
}//this will bring every posts user has created
})
I think above coding standard will make your code looks cleaner and will be more helpful for larger projects.
Hope this helps.

Resources