I'm trying to learn Sequelize.js and I'm confused about its Many-To-Many Association.
What I've tried to do is simple Tasks Management with Users. Each task can be assignable to other users.
So, there's Users, Tasks and TaskContributors tables.
First, I made the POC version of my idea with Express.js. Then I found that I don't know how to insert to the Relational Table within different router.
Here are the model codes.
User Model
'use strict'
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
'User',
{
uid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 },
name: DataTypes.STRING,
password: DataTypes.STRING,
},
{},
)
User.associate = function (models) {
User.belongsToMany(models.Task, {
as: 'Contributors',
through: 'TaskContributors',
foreignKey: 'userId',
})
}
return User
}
Task Model
'use strict'
module.exports = (sequelize, DataTypes) => {
const Task = sequelize.define(
'Task',
{
uid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 },
name: DataTypes.STRING,
description: DataTypes.TEXT,
status: DataTypes.BOOLEAN,
},
{},
)
Task.associate = function (models) {
Task.belongsToMany(models.User, {
as: 'Task',
through: 'TaskContributors',
foreignKey: 'taskId',
})
}
return Task
}
TaskContributor Model
'use strict'
module.exports = (sequelize, DataTypes) => {
const TaskContributor = sequelize.define(
'TaskContributor',
{
userId: {
allowNull: false,
type: DataTypes.INTEGER,
references: { model: 'Users', key: 'id' },
},
taskId: {
allowNull: false,
type: DataTypes.INTEGER,
references: { model: 'Tasks', key: 'id' },
},
userStatus: { allowNull: false, type: DataTypes.STRING },
},
{},
)
TaskContributor.associate = function (models) {}
return TaskContributor
}
Routers
Users Router
router.get('/create/:name', (req, res) => {
User.create({ name: req.params.name, password: '123' })
.then((result) => res.send(result))
.catch((err) => res.status(500).send(err))
})
Tasks Router
router.get('/create/:userId/:name', (req, res) => {
const { userId, name } = req.params
User.findOne({ where: { uid: userId } }).then(async (user) => {
console.log(user)
const maybeTask = await user
.addTask({
name,
description: '',
status: false,
through: { userStatus: 'owner' },
})
.catch((err) => err)
if (maybeTask instanceof Error) res.status(500).send(maybeTask)
res.send(maybeTask)
})
})
When I tried to create new Task, it said user.addTask is not a function.
From what I understand from the Docs is that they showed how to create M-M association with two model.create() Objects, but not with the scenario like creating in different file.
Related
Stack: NodeJs, PostgreSQL, Sequelize, Express.
I have two associated models (User and Role) through User_Roles.
When I create new user via Sequelize.create(), how can i fill the User_Roles table to define which roles (i get the array filled with role ID) the user have. The roles are already defined in table (roles)
The models:
const User = sequelize.define('users', {
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
email: { type: DataTypes.STRING(320), allowNull: false, unique: true, isEmail: true },
});
const Role = sequelize.define('roles', {
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
title: { type: DataTypes.STRING(64), allowNull: false, unique: true },
});
const User_Roles = sequelize.define('user_roles', {}, { timestamps: false });
// Associations
User.belongsToMany(Role, { through: User_Roles });
Role.belongsToMany(User, { through: User_Roles });
UserController:
async create(req, res, next) {
const { email, roles } = req.body;
if (!email) {
return next(ApiError.badRequest('Invalid request'));
}
if (!Array.isArray(roles) || !roles.length) {
return next(ApiError.badRequest('Invalid request'));
}
const isExistThisEmail = await User.findOne({ where: { email } });
if (isExistThisEmail) {
return next(ApiError.badRequest('The email already exists'));
}
const user = await User.create({ email });
return res.status(201).json(createdUser);
}
You can use dynamically added methods (according to associations) in the model:
await user.setRoles([1,2,3]) // or indicate the roles instances
See special methods/mixins
I am implementing a feature that will allow users to follow accounts and be followed, I made my schema like this
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Follows', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
followerId: {
allowNull: false,
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id'
},
onDelete: 'CASCADE',
},
followingId: {
allowNull: false,
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id'
},
onDelete: 'CASCADE',
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Follows');
}
};
Association tables with the Users table
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Follow 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
models.Follow.belongsTo(models.User, {
as: 'follower',
foreignKey: 'followerId',
});
models.Follow.belongsTo(models.User, {
as: 'following',
foreignKey: 'followingId',
});
}
};
Follow.init({
followerId: DataTypes.INTEGER,
followingId: DataTypes.INTEGER,
}, {
sequelize,
modelName: 'Follow',
});
return Follow;
};
I don't know if I made a mistake in my associations
Controllers
const models = require('../models');
const { Follow, User } = models;
module.exports.follow = async (req, res) => {
const { id } = req.params
const { followerId, followingId } = req.body;
try {
const userFollowing = await User.findByPk(id);
if (userFollowing) {
if (!userFollowing) {
res.status(401).json("Cet utilisateur n'est pas disponible")
}
// console.log(userFollowing);
Follow.update({ followingId: followerId }, { where: { id } }).then((user) => {
if (user) {
userFollowing.update({
following: followerId
})
return res.status(201).json({ "Vous suivez maintenant": user.following })
}
else {
return res.status(400).json('Impossible de suivre cette personne 😢');
}
})
}
const userFollower = await User.findByPk(followingId);
if (userFollower) {
if (!userFollower) {
res.status(401).json("Cet utilisateur n'est pas disponible")
}
//console.log(userFollower);
Follow.update({ followerId: userFollower }, { where: { id } }).then((user) => {
if (user) {
userFollower.update({
followers: followingId
})
res.status(200).json({ "Vous êtes suivi maintenant par ": userFollower.fristname + userFollower.lastname })
}
else {
if (err) return res.status(400).json('Impossible de suivre cette personne 😢');
}
}).catch((err) => res.status(400).json(err))
}
} catch (err) {
return res.status(500).json("err" + err)
}
}
Here the request is well executed and sends a 201 response to update the element the problem is that the response is not added in the database
Im trying to create a sequalized query that Searches For a Team Name & Filter To Sport Type but when I test the JSON body on Postman I get an error and its not returning the data. The JSON body matches the data that its meant to return but its not matching up.
Test on Postman
{
"searchTerm": "Kaizer fc",
"sportType": "2"
}
teams.js
const Teams = require('../models').teams;
const {sequelize, QueryTypes } = require('sequelize');
const db = require('../models')
const search = (searchTerm, sportType) => {
return new Promise(async (resolve, reject) => {
try {
console.log("Testing");
const teams = await db.sequelize.query(
`SELECT teams.name, institutions.name, addresses.line1, addresses.line2, addresses.country
FROM teams
INNER JOIN institutions
ON teams.institution_id = institutions.id
INNER JOIN addresses
ON institutions.address_id = addresses.id
WHERE teams.name like :search_name and teams.sport_type_id LIKE :sport_type`,
{
replacements: { search_name: searchTerm, sport_type: sportType },
type: QueryTypes.SELECT
}
);
return resolve(teams);
} catch (err) {
return reject(err)
}
})
}
teams.js - Models Folder
'use strict';
module.exports = (sequelize, DataTypes) => {
const teams = sequelize.define('teams', {
name: { type: DataTypes.STRING, allowNull: false },
media_id: { type: DataTypes.STRING, allowNull: true },
state_id: { type: DataTypes.INTEGER, allowNull: true },
status_id: { type: DataTypes.INTEGER, allowNull: true },
sport_type_id: { type: DataTypes.INTEGER, allowNull: false },
created_by: { type: DataTypes.INTEGER, allowNull: true, references: { model: 'users', key: 'id' } },
modified_by: { type: DataTypes.INTEGER, allowNull: true, references: { model: 'users', key: 'id' } },
primary_user_id: { type: DataTypes.INTEGER, allowNull: true, references: { model: 'users', key: 'id' } },
institution_id: { type: DataTypes.INTEGER, allowNull: true, references: { model: 'institutions', key: 'id' } },
}, {
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
unique: true,
fields: ['id']
}
],
});
return teams;
};
search.js - Controllers
const helper = require('../utils/helper');
const teamsService = require('../services/teams');
const search = async (req, res) => {
try {
const data = await teamsService.search(req.body);
console.log("TESTING");
console.log(data);
return res.send(data);
} catch (err) {
return helper.handleError(err, req, res);
}
}
module.exports = search;
search function has two arguments: searchTerm and sportType and you pass the whole req.body as a first argument so that's why it becomes a value for searchTerm and you got this error about the whole value from Sequelize.
Just extract both props from req.body OR define search with props passed by as an object:
const { searchTerm, sportType } = req.body;
const data = await teamsService.search(searchTerm, sportType);
OR
const data = await teamsService.search(req.body);
...
const search = ({ searchTerm, sportType }) => {
return new Promise(async (resolve, reject) => {
I try to create a new researcher but I just go into catch, and I do not get any errors. I am new using sequelize, I need a lot of help for this problem, my complete code in git: https://github.com/chanudinho/RevYou-BackEnd.
I can't explain it better, please if you need to download the project and test it. Sorry for my english =x
researcherController.js
const Researcher = require('../../sequelize/models/researcher');
const createResearcher= async (req, res) => {
try{
Researcher.create({name: 'name', email: 'email', password: 'password'});
return res.status(201).send('sucesso');
}catch (err){
return res.status(500).send('error');
}
}
models/researcher.js
module.exports = (sequelize, DataTypes) => {
const Researcher = sequelize.define('Researcher', {
name: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING
});
return Researcher;
};
migrations/20190114200431-create-researcher
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Researcher', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
allowNull: false,
type: Sequelize.STRING
},
email: {
allowNull: false,
type: Sequelize.STRING
},
password:{
allowNull: false,
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Researcher');
}
};
models/index.js
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const config = require('../../config/database.js');
const db = {};
const sequelize = new Sequelize(config);
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;
});
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
The problem is that you are importing the model file and this is not right, your index.js within model maps all model files by adding the sequelize instance and the datatypes. You should always import the index.
If you import the model index and give a console.log() in it will see that you have the object of your model and the instance of the sequelize.
const db = require('../../sequelize/models/index');
console.log(db)
Inside the exit will have something like this: Example:
Researcher: Researcher,
sequelize:
Sequelize { ....
To access your model you can do the following. By using destructuring assignment, you extract the model from within the index.
Result
const { Researcher } = require('../../sequelize/models/index')
const createResearcher= async (req, res) => {
try{
await Researcher.create({name: 'name', email: 'email', password: 'password'});
return res.status(201).send('sucesso')
}catch (err){
return res.status(500).send('error');
}
}
Whenever you create a new file inside the model folder, it will be mapped by index.js and added inside the matrix and using destructuring you can access or use the matrix key itself.
const db = require('../../sequelize/models/index')
const createResearcher= async (req, res) => {
try{
await db.Researcher.create({name: 'name', email: 'email', password: 'password'});
return res.status(201).send('sucesso')
}catch (err){
return res.status(500).send('error');
}
}
Here's my query that's failing:
Models.OrdersProducts.create({
orderId: 1,
productId: 1,
});
with the error:
Executing (default): INSERT INTO "OrdersProducts" ("orderId","productId","createdAt","updatedAt","OrderId") VALUES (1,1,'2018-02-25 12:51:00.110 +00:00
','2018-02-25 12:51:00.110 +00:00',NULL) RETURNING *;
Unhandled rejection SequelizeDatabaseError: column "OrderId" of relation "OrdersProducts" does not exist
at Query.formatError (/Users/aakashverma/Documents/sequelize-join-table/node_modules/sequelize/lib/dialects/postgres/query.js:363:16)
where my table doesn't have the column OrderId(with capital O) and only orderId
NOTE: My Orders and Products tables do have an entry with id 1, that's not the problem.
This is my migration for OrdersProducts:
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('OrdersProducts', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
orderId: {
type: Sequelize.INTEGER,
references: {
model: 'Orders',
key: 'id',
},
},
productId: {
type: Sequelize.INTEGER,
references: {
model: 'Products',
key: 'id',
},
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
}),
down: (queryInterface, Sequelize) => queryInterface.dropTable('OrdersProducts'),
};
and here are the models file:
models/OrdersProducts.js
module.exports = (sequelize, DataTypes) => {
const OrdersProducts = sequelize.define('OrdersProducts', {
orderId: DataTypes.INTEGER,
productId: DataTypes.INTEGER,
}, {});
return OrdersProducts;
};
models/Orders.js
module.exports = (sequelize, DataTypes) => {
const Orders = sequelize.define('Orders', {
userId: DataTypes.INTEGER,
}, {});
Orders.associate = function (models) {
Orders.belongsTo(models.Users);
Orders.belongsToMany(models.Products, { through: 'OrdersProducts', as: 'product' });
};
return Orders;
};
models/Products.js
module.exports = (sequelize, DataTypes) => {
const Products = sequelize.define('Products', {
name: DataTypes.TEXT,
}, {});
Products.associate = function (models) {
Products.belongsToMany(models.Orders, { through: 'OrdersProducts', as: 'order' });
};
return Products;
};
Weird.
N.B. I am using 1 as the id in queries because this is the autoincrement id key value as I only add one entry in any table whatsoever.
Looking at my Orders model:
module.exports = (sequelize, DataTypes) => {
const Orders = sequelize.define('Orders', {
userId: DataTypes.INTEGER,
}, {});
Orders.associate = function (models) {
Orders.belongsTo(models.Users);
Orders.belongsToMany(models.Products, { through: 'OrdersProducts', as: 'product' });
};
return Orders;
};
and myProducts model:
module.exports = (sequelize, DataTypes) => {
const Products = sequelize.define('Products', {
name: DataTypes.TEXT,
}, {});
Products.associate = function (models) {
Products.belongsToMany(models.Orders, { through: 'OrdersProducts', as: 'order' });
};
return Products;
};
These two seem to create attributes of their own for my OrdersProducts table as OrderId and ProductId (with first letter capital; don't know why and how they are generating the name) and therefore, I was getting that OrderId thingy.
If I remove the belongToMany association from the Orders model, I stared to get the same error with ProductId column instead.
Finally, I found the accepted answer here which told me, in a way, to use the columns provided by the associations and drop the corresponding in the OrdersProducts column.
Now my query, which works fine BTW, looks like:
const Models = require('./models');
// Models.Users.create({
// name: 'Aakash',
// });
// Models.Orders.create({
// userId: 1,
// });
// Models.Products.create({
// name: 'lash',
// });
Models.OrdersProducts.create({
OrderId: 1,
ProductId: 1,
});
and model looks like (pay attention to the capital letters, again):
module.exports = (sequelize, DataTypes) => {
const OrdersProducts = sequelize.define('OrdersProducts', {
OrderId: DataTypes.INTEGER,
ProductId: DataTypes.INTEGER,
}, {});
return OrdersProducts;
};
The mystery about the automatic column naming still exists.
P.S. This is the weirdest part.
Now I have a table Users as you'd have been able to see from my code above. Pay attention to how the belongsTo plays in the models.
models/users.js
module.exports = (sequelize, DataTypes) => {
const Users = sequelize.define('Users', {
name: DataTypes.STRING,
}, {});
Users.associate = function (models) {
Users.hasMany(models.Orders);
};
return Users;
};
models/orders.js
module.exports = (sequelize, DataTypes) => {
const Orders = sequelize.define('Orders', {
userId: DataTypes.INTEGER,
}, {});
Orders.associate = function (models) {
Orders.belongsTo(models.Users);
Orders.belongsToMany(models.Products, { through: 'OrdersProducts', as: 'product' });
};
return Orders;
};
The query below still works and adds an entry to Orders (look at how it doesn't require me to remove userId column from the Orders table nor does it come up with its own UserId -_-):
Models.Users.create({
name: 'Aakash',
});
Models.Orders.create({
userId: 1,
});
models/users.js
module.exports = (sequelize, DataTypes) => {
const Users = sequelize.define('Users', {
name: DataTypes.STRING,
}, {});
Users.associate = function (models) {
Users.hasMany(models.Orders);
};
return Users;
};
models/orders.js
module.exports = (sequelize, DataTypes) => {
const Orders = sequelize.define('Orders', {
userId: DataTypes.INTEGER,
}, {});
Orders.associate = function (models) {
Orders.belongsTo(models.Users);
Orders.belongsToMany(models.Products, { through: 'OrdersProducts', as: 'product' });
};
return Orders;
};