How to fetch associated data with models generated form Sequalize? - mysql

I have been developing a NodeJs backend application with an MySQL support with Sequelize as the ORM. I'm trying to fetch data by calling an API that I have created. It sends the data as an response. But it doesn't containg the associated object related to the foriegn key relationships.
I already had a MySQL database and I was using sequelize ORM, I used sequelize-auto to generate the model classes. All the model classes were generated successfully. But the associations were not generated with the models. Therefore to cater to the associations I had to manually add the associations to the model class. Then I create the route files and created the HTTP GET method. But the API endpoint doesn't send the data as expected.
following shows the model classes and route files that I have created.
module.exports = function(sequelize, DataTypes) {
const Department = sequelize.define('Department', {
department_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(100),
allowNull: false
},
description: {
type: DataTypes.STRING(1000),
allowNull: true
}
}, {
tableName: 'department',
timestamps: false,
underscored: true
});
Department.associate = function(models) {
// associations can be defined here
Department.hasMany(models.Category, {
foreignKey: 'department_id',
as: 'categories',
});
};
return Department;
};
module.exports = function(sequelize, DataTypes) {
const Category = sequelize.define('Category', {
category_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
department_id: {
type: DataTypes.INTEGER(11),
allowNull: false
},
name: {
type: DataTypes.STRING(100),
allowNull: false
},
description: {
type: DataTypes.STRING(1000),
allowNull: true
}
}, {
tableName: 'category',
timestamps: false,
underscored: true
});
Category.associate = function(models) {
// associations can be defined here
Category.belongsTo(models.Department)
};
return Category;
};
var express = require('express');
var router = express.Router();
var model = require('../models/index');
/* GET departments listing. */
router.get('/', function(req, res, next) {
model.Department.findAll({})
.then(department => res.json({
error: false,
data: department
}))
.catch(error => res.json({
data: [],
error: true
}));
});
module.exports = router;
var express = require('express');
var router = express.Router();
var model = require('../models/index');
/* GET category listing. */
router.get('/', function(req, res, next) {
model.Category.findAll({})
.then(category => res.json({
error: false,
data: category
}))
.catch(error => res.json({
data: [],
error: true
}));
});
module.exports = router;
Response for /department route
{
"error": false,
"data": [
{
"department_id": 1,
"name": "Regional",
"description": "Proud of your country? Wear a T-shirt with a national symbol stamp!"
},
{
"department_id": 2,
"name": "Nature",
"description": "Find beautiful T-shirts with animals and flowers in our Nature department!"
},
{
"department_id": 3,
"name": "Seasonal",
"description": "Each time of the year has a special flavor. Our seasonal T-shirts express traditional symbols using unique postal stamp pictures."
}
]
}
Response for /category route
{
"data": [],
"error": true
}
I was expecting data to come with associated objects as well. But it doesn't send the data as expected. What have I done wrong here.

The thing here is that you are not saying on the query that you need the associate data. To do this you have to use include inside the findAll() function.
/* GET departments listing. */
router.get('/', function(req, res, next) {
model.Department.findAll({
include: [{
model: model.Category
as: 'categories'
}]
})
.then(department => res.json({
error: false,
data: department
}))
.catch(error => res.json({
data: [],
error: true
}));
});
Edit:
I saw that you changed the default primary key for the models, so you also has to specify that on the Category association. Sequelize by default only works with one way association, on your case from Deparment to Category. Sequelize doesn't know about Deparment foreignkey on Category, even when you defined it. You have to define which key are you pointing.
Category.associate = function(models) {
// associations can be defined here
Category.belongsTo(models.Department, {as: 'department', foreignKey: 'department_id'})
};

Related

Sequelize N-M association doesn't recognize the include model

I am trying to do a many-to-many assocition with sequelize but I constantly having the same error without reason...
Here is the error I get : SequelizeEagerLoadingError: User is not associated to UserTool!
I searched on the web and my code is corresponding to many right answers that I found.
The M-M association goes like this : User-UserTool-Tool, UserTool is the bridge table between User and Tool
Here is the following code :
Model User - User.js
const Sequelize = require('sequelize');
var db = require('../../config/database');
const User = db.define('User', {
id: {
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
}, {
timestamps: false,
freezeTableName: true,
tableName: 'User'
});
module.exports = User;
Model Tool - Tool.js
const Sequelize = require('sequelize');
var db = require('../../config/database');
const Tool = db.define('Tool', {
id: {
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
}, {
timestamps: false,
freezeTableName: true,
tableName: 'Tool'
});
module.exports = Tool;
Model UserTool - UserTool.js
const Sequelize = require('sequelize');
var db = require('../../config/database');
const UserTool = db.define('UserTool', {
userId: {
type: Sequelize.INTEGER
},
toolId: {
type: Sequelize.INTEGER
},
status: {
type: Sequelize.STRING
},
active: {
type: Sequelize.STRING
},
}, {
timestamps: false,
freezeTableName: true,
tableName: 'UserTool'
});
module.exports = UserTool;
Main.js
const Sequelize = require('sequelize');
var db = require('../../config/database');
const User = require('./User');
const Tool = require('./Tool');
const UserTool = require('./UserTool');
User.belongsToMany(Tool, { through: UserTool });
Tool.belongsToMany(User, { through: UserTool });
module.exports = {
User,
Tool,
UserTool
}
And then when I try to do a findAll() with an include, I get the error mentionned previously.
Here is the code of the findAll with the include
Find.js
const {User, UserTool} = require('./Main');
exports.FindAllUsers = async function (params) {
let message;
await UserTool.findAll({
where: {
userId: params.userId
},
include: [
{
model: User
},
]
});
As I understand it, if you want to query all users. Simply use the following method:
const result = await User.findAll();
If you want to query the user with id = 1 and all tools for that user, using:
const result = await User.findAll({
where: { id: '1' },
include: [{ model: Tool }],
});
sequelize will query the appropriate Tools for User based on the join table UserTool. Eager loading needs both sides(User and Tool) of the model are established associations(belongsToMany). UserTool doesn't establish associations with User, so you can't use include options, that's why sequelize throw that error.

How to use findAll with associations in Sequelize

I'm having problems to use the findAll() method with associations from Sequelize.
I have two models: Posts and Authors (an author has many posts and one post has one author), that I have created with Sequelize-cli and then through the migration command npx sequelize db migrate:all i have created them in mysql. To keep things organized, I have the associations between the models in another migration file (created with npx sequelize init:migrations, after all the models already existent), so my code looks like this:
AUTHOR MODEL
'use strict';
module.exports = (sequelize, DataTypes) => {
const Author = sequelize.define('Author', {
authorName: {
type: DataTypes.STRING,
validate: {
is: ["^[a-z]+$",'i'],
}
},
biography: {
type: DataTypes.TEXT,
validate: {
notEmpty: true,
}
}
}, {});
Author.associate = function(models) {
Author.hasMany(models.Post);
};
return Author;
};
POST MODEL
'use strict';
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
title: {
type: DataTypes.STRING,
validate: {
is: ["^[a-z]+$",'i'],
notEmpty: true,
},
},
content: {
type: DataTypes.TEXT,
validate: {
notEmpty: true,
},
},
likes: {
type: DataTypes.INTEGER,
defaultValue: 0,
validate: {
isInt: true,
},
},
}, {});
Post.associate = function(models) {
// associations can be defined here
};
return Post;
};
ASSOCIATIONS FILE (MIGRATION) (showing only parts that matter)
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction(t => {
return Promise.all([
queryInterface.addColumn('Posts','AuthorId', {
type: Sequelize.INTEGER,
references: {
model: 'Authors',
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
}, { transaction: t }),
queryInterface.addColumn('Posts', 'ImagesId', {
type: Sequelize.INTEGER,
references: {
model: 'Images',
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
}, { transaction: t }),
queryInterface.addColumn('Posts', 'CategoryId', {
type: Sequelize.INTEGER,
references: {
model: 'Categories',
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
}, { transaction: t }),
]);
});
This is working fine apparently, since in Mysql-Workbench it shows me the following:
But, when I try to use the findAll() like this:
const { Post, Author } = require('../models/index');
function(response) {
Post.findAll({
attributes: ['id', 'title', 'content', 'likes'],
include: {
model: Author,
}
})
.then(result => response.json(result))
.catch(error => response.send(`Error getting data. Error: ${error}`));
It gives me the following error:
SequelizeEagerLoadingError: Author is not associated to Post!
So, I dont know anymore how to proceed. I've been trying many others approaches, but all of then unsuccessfully. I read already many other questions here in StackOverFlow about how to solve this sort of problem, but those were unsuccessfully too.
Thanks in advance.
You need to define the association for Post also as you are querying upon Post model
Post.associate = function(models) {
Post.belongsTo((models.Author);
};
You need to add an association from both ends, Post -> Author and Author -> Post , this way you will never stuck in this kind of error.
Summarizing this documentation we have the following:
If you have these models:
const User = sequelize.define('user', { name: DataTypes.STRING });
const Task = sequelize.define('task', { name: DataTypes.STRING });
And they are associated like this:
User.hasMany(Task);
Task.belongsTo(User);
You can fetch them with its associated elements in these ways:
const tasks = await Task.findAll({ include: User });
Output:
[{
"name": "A Task",
"id": 1,
"userId": 1,
"user": {
"name": "John Doe",
"id": 1
}
}]
And
const users = await User.findAll({ include: Task });
Output:
[{
"name": "John Doe",
"id": 1,
"tasks": [{
"name": "A Task",
"id": 1,
"userId": 1
}]
}]

Sequelize.js: Include unexpected. Element has to be either a Model, an Association or an object

I'm using Sequelize.js in my Node.js application and keep running into a very strange problem.
Background: I have two models, Account and AccountCategory as follows. My API endpoint calls the route /accounts which calls the accounts controller to do an Account.findAll() query.
Accounts model has a defaultScope to include the related category by default, without having to specify it each time inside the findAll({}) block.
Problem: When the Accounts model is attempting to access and return the data from the database, the defaultScope is trying to include the AccountCategory, Sequelize throws the error:
Include unexpected. Element has to be either a Model, an Association or an object.
I suspect it has to do with the fact that AccountCategory is placed after Account in my models folder when the models are being set up and thus not processed (associated). I base this on the fact that other associations like User and Role (ie. a user has a role) are fine using the same method (ie. no problem with path depth as this answer suggests).
I've spent the last 2 days trying to get the defaultScope working and stop producing this error without any luck. Similar questions do not provide an answer and I would greatly appreciate any help resolving this problem. Thanks.
Account:
module.exports = (sequelize, DataTypes) => {
const Account = sequelize.define(
"Account",
{
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(100)
},
category_id: {
type: DataTypes.INTEGER(11),
allowNull: false
}
},
{
timestamps: false,
tableName: "Account",
defaultScope: {
include: [{
model: sequelize.models.AccountCategory,
as: "category"
}]
}
}
);
Account.associate = models => {
// Association: Account -> AccountCategory
Account.belongsTo(models.AccountCategory, {
onDelete: "CASCADE",
foreignKey: {
fieldName: "category_id",
allowNull: false,
require: true
},
targetKey: "id",
as: "category"
});
};
return Account;
};
Account Category:
module.exports = (sequelize, DataTypes) => {
var AccountCategory = sequelize.define(
"AccountCategory",
{
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(30),
allowNull: false,
unique: true
}
},
{
timestamps: false,
tableName: "Account_Category"
}
);
return AccountCategory;
};
Models Index:
const fs = require("fs");
const path = require("path");
const Sequelize = require("sequelize");
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || "development";
const db = {};
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASS,
{
host: process.env.DB_HOST,
dialect: "mysql",
operatorAliases: false,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
}
);
fs.readdirSync(__dirname)
.filter(function(file) {
return (
file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
);
})
.forEach(function(file) {
var model = sequelize["import"](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(function(modelName) {
if (db[modelName].associate) {
db[modelName].associate(db);
}
db[modelName].associate(db);
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
You are correct when you say:
I suspect it has to do with the fact that AccountCategory is placed after Account in my models folder when the models are being set up and thus not processed (associated).
TLDR: Add a new function to your model class definition similar to the associate function, and use the addScope function to define any scopes that reference other models that may have not been initialized due to file tree order. Finally, call that new function the same way you call db[modelName].associate in your models.index.js file.
I had a similar problem and solved it by defining any scopes that reference any models, e.g. in an include, after all the models are initialized after running the following in your models/index.js file.
Here is an example:
models/agent.js
'use strict';
const { Model } = require('sequelize');
const camelCase = require('lodash/camelCase');
const { permissionNames } = require('../../api/constants/permissions');
module.exports = (sequelize, DataTypes) => {
/**
* #summary Agent model
*/
class Agent extends Model {}
Agent.init(
{
id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
lastName: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
scopes: {
// Works because the agent-role.js file / model comes before agent.js in the file tree
[camelCase(permissionNames.readAgentRoles)]: {
include: [
{
model: sequelize.models.AgentRole,
},
],
},
// Doesn't work due to import order in models/index.js, i.e., agent.js is before role.js in the file tree
// [camelCase(permissionNames.readRoles)]: {
// include: [
// {
// model: sequelize.models.Role,
// },
// ],
// },
},
}
);
Agent.associate = function (models) {
Agent.belongsToMany(models.Role, {
through: 'AgentRole',
onDelete: 'CASCADE', // default for belongsToMany
onUpdate: 'CASCADE', // default for belongsToMany
foreignKey: {
name: 'agentId',
type: DataTypes.INTEGER,
allowNull: false,
},
});
Agent.hasMany(models.AgentRole, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
foreignKey: {
name: 'agentId',
type: DataTypes.INTEGER,
allowNull: false,
},
});
};
// Add a custom `addScopes` function to call after initializing all models in `index.js`
Agent.addScopes = function (models) {
Agent.addScope(camelCase(permissionNames.readRoles), {
include: [
{
model: models.Role,
},
],
});
};
return Agent;
};
models/index.js
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const config = require('../database-config.js');
const db = {};
const sequelize = new Sequelize(config.database, config.username, config.password, config);
/**
* Import and attach all of the model definitions within this 'models' directory to the sequelize instance.
*/
fs.readdirSync(__dirname)
.filter((file) => {
return file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js';
})
.forEach((file) => {
// Here is where file tree order matters... the sequelize const may not have the required model added to it yet
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
db[model.name] = model;
});
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
// We need to add scopes that reference other tables once they have all been initialized
if (db[modelName].addScopes) {
db[modelName].addScopes(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
Goodluck!

Able to make migration with associations but not able to query with associations (association not found)

I've built a database with a couple tables that are associated with each other: Auction and Bids. Each Auction should have many Bids and each Bid should have only one Auction. I made a migration fine to add foreignKeys but when I try to look up a certain Bid on an Auction I receive a SequelizeEagerLoadingError: Bids is not associated to Auctions! error.
migrations file:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'Auctions', // name of target model
'BidId',
{
type: Sequelize.INTEGER,
references:{
model: "bids",
key: "bid_id",
},
},
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Auction');
}
};
bids.js
const Sequelize = require('sequelize');
// const resolver = require('graphql-sequelize');
const sequelize = require('../config/database');
const Auction = require('./Auction');
const tableName = 'bids';
const Bids = sequelize.define('Bids', {
bid_id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
createdAt: {
type: Sequelize.DATE,
// defaultValue: Sequelize.NOW
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
},
amount: {
type: Sequelize.INTEGER
},
bid_amount: {
type:Sequelize.STRING
},
bid_no: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
},
},
{tableName})
Bids.associate = () => {
Bids.hasOne(Auction, {foreignKey:"BidId"})
};
auction.js
const Sequelize = require('sequelize');
const sequelize = require('../config/database');
const tableName = 'Auctions';
const Auction = sequelize.define('Auctions', {
auc_id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4, // generate the UUID automatically
primaryKey: true,
},
features: {
type: Sequelize.JSONB,
},
bid_amount: {
type:Sequelize.STRING
},
BidId: {
type: Sequelize.UUID,
}
}, { tableName });
Auction.hasMany(Bids, {foreignKey: 'BidId'})
module.exports = Auction
query.js
const findBidOnAuction = () => {Auction.findOne({where:{BidId:2}, include:[{model:Bids}]}).then(data => console.log("result", data))}
How do I properly associate these tables?
edit: Also on pgAdmin I can that the relationship exists, BidId is a foreignKey on Auction linked to bid_id on Bids
I do the following for add a foreignKey on migrations:
1.I create a migration for the parent model with a ref function.
const TABLE_NAME = 'parent';
function up(queryInterface, Sequelize) {
return queryInterface.createTable(
TABLE_NAME, //Bid model
{
//attributes
},
);
}
function down(queryInterface, Sequelize) {
return queryInterface.dropTable(TABLE_NAME);
}
function ref(Sequelize) {
return {type: Sequelize.STRING(2), references: {model: TABLE_NAME, key: 'id'}}; // 'id' here is your parent (Bids) primary key
}
module.exports = {up, down, ref};
2.On the child model where you are going to add the reference, you import the ref function and added like this, so your migration actually knows which model are you refering:
const {ref: REF_Model} = require('name-of-your-migration-file'); //import model
async function up(queryInterface, Sequelize){
await queryInterface.addColumn('Auctions', 'BidId', {...REF_Model(Sequelize), allowNull: true});
}
async function down(queryInterface){
await queryInterface.removeColumn('Auctions', 'BidId');
}
module.exports = {up, down};

How use JOIN LEFT in Sequelize?

I have two models tables, like
Project = Sequelize.define('Project',{
name: Sequelize.STRING
});
Task = Sequelize.define('Task',{
name:Sequelize.STRING,
status: Sequelize.BOOLEAN,
deadline: Sequelize.DATE,
from_name: Sequelize.STRING,
ProjectId: Sequelize.INTEGER
});
I need to find all records from two tables. How do I perform this query in Sequelize?
SELECT * FROM mydb.Projects
LEFT JOIN mydb.Tasks ON Projects.id = Tasks.Project_id
I using:
exports.list = function (req, res) {
Project.hasMany(Task,{ foreignKey: { allowNull: false }, onDelete: 'CASCADE' });
Project.all({
include: [{
model: Task,
required:false,
where:{ProjectId: Sequelize.col('Project.id')}
}]
})
.then(function (projects) {
res.render('TODOList', {title: 'TODO', projects: projects || [] });
console.log(projects);
})
.catch(function (err) {
if (err) {
res.render('TODOList', {title: 'TODO List'});
}
});
};
but I get records only from Project, like:
Project {
dataValues: {
id: 5,
name: 'pr2',
createdAt: 2017-08-20T07:03:09.000Z,
updatedAt: 2017-08-20T07:41:47.000Z,
Tasks: [Object]
}
}
How I can get records Task (Project.dataValues.Task)?
So models are (say it: db/models.js):
const
Project = Sequelize.define(
'Project',
{
name: Sequelize.STRING
}),
Task = Sequelize.define(
'Task',
{
name: Sequelize.STRING,
status: Sequelize.BOOLEAN,
deadline: Sequelize.DATE,
from_name: Sequelize.STRING,
ProjectId: Sequelize.INTEGER
});
// relations must be defined once in model definition part (not like You do in every request
Project.hasMany(Task, {foreignKey: {allowNull: false}, onDelete: 'CASCADE'});
Task.belongsTo(Project);
module.exports = {Project, Task};
And in Your router's handler (say it routes/projects/list.js):
const Sequelize = require('sequelize');
const {Project, Task} = require('../../db/models');
module.exports.list = (req, res) => {
const query = {
include: [{
model: Task,
as: 'tasks',
required: false
}]
};
Project
.all(query)
.then(function (projects) {
// it's not recommended, but You insist to get read of model instances and have normal array of objects
projects = projects ? projects.map(project => project.toJSON()) : [];
res.render(
'TODOList',
{
title: 'TODO',
projects
});
console.log(projects);
})
.catch(function (err) {
console.error(err);
res.render('TODOList', {title: 'TODO List'});
});
};
but since sequelize returns array of sequelize model instances - it's normal to have dataValues don't worry just use as usual:
for(let project of projects) {
console.log(project.tasks[0].name);
}