How to make a sub query (or join) in GraphQl? - node.js

I am trying to wrap my head around GraphQl, more precisely on sub queries.
I have two tables: Products and Likes, the two have a common "userId" column. How could I count the total number of likes for each product from the Likes table based on the common column?
schema:
import { makeExecutableSchema } from 'graphql-tools';
import resolvers from './resolvers';
const schema = `
type Products {
id: Int!
name: String
userid: String
}
type Likes {
id: Int!
userid: String
}
# the schema allows the following query:
type Query {
products: [Products]
}
`;
export default makeExecutableSchema({
typeDefs: schema,
resolvers,
});
resolver:
import { Products, Likes } from './connection.js';
const resolveFunctions = {
Query: {
likes() {
return Likes.findAll();
},
products() {
return Products.findAll();
},
},
};
export default resolveFunctions;
connection.js
...
const Products = db.define('products', {
id: {
type: Sequelize.INTEGER,
primaryKey: true
},
name: Sequelize.STRING,
userid: Sequelize.STRING,
title: Sequelize.STRING,
},
{
timestamps: false
}
);
const Likes = db.define('likes', {
id: {
type: Sequelize.INTEGER,
primaryKey: true
},
userid: Sequelize.STRING
},
{
timestamps: false
}
);
export { Products, Likes };

Related

Postgress tries to return a column that does not exist in my model

I use graphql API and try to insert data into Postgress table and got an error:
"message": "column \"UserId\" does not exist",
and my raw query:
Executing (default): INSERT INTO "Recipes"
("id","title","ingredients","direction","createdAt","updatedAt","userId") VALUES
(DEFAULT,$1,$2,$3,$4,$5,$6)
RETURNING
"id","title","ingredients","direction","createdAt","updatedAt","userId","UserId";
the problem is that a column UserId isn't in my model but userId is ! And i don't know why Postgres trying to return UserId column.
My Models.
User:
module.exports = (sequelize, DataTypes) => {
class User extends Model {
static associate(models) {
User.hasMany(models.Recipe)
}
};
User.init({
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false
},
password: {
type: DataTypes.STRING,
allowNull: false
}
}, {
sequelize,
modelName: 'User',
});
return User;
};
Recipe:
module.exports = (sequelize, DataTypes) => {
class Recipe extends Model {
static associate(models) {
Recipe.belongsTo(models.User, { foreignKey: 'userId' })
}
};
Recipe.init({
title: {
type: DataTypes.STRING,
allowNull: false
},
ingredients: {
type: DataTypes.STRING,
allowNull: false
},
direction: {
type: DataTypes.STRING,
allowNull: false
}
}, {
sequelize,
modelName: 'Recipe',
});
return Recipe;
};
my graphql schema:
const typeDefs = gql`
type User {
id: Int!
name: String!
email: String!
recipes: [Recipe!]!
}
type Recipe {
id: Int!
title: String!
ingredients: String!
direction: String!
user: User!
}
type Query {
user(id: Int!): User
allRecipes: [Recipe!]!
recipe(id: Int!): Recipe
}
type Mutation {
createUser(name: String!, email: String!, password: String!): User!
createRecipe(
userId: Int!
title: String!
ingredients: String!
direction: String!
): Recipe!
}
`
graphql reslover:
Mutation: {
async createRecipe (root, { userId, title, ingredients, direction }, { models }) {
return models.Recipe.create({ userId, title, ingredients, direction })
}
}
and my grapql request:
mutation {
createRecipe(
userId: 1
title: "Sample 2"
ingredients: "Salt, Pepper"
direction: "Add salt, Add pepper"
) {
id
title
ingredients
direction
}
}
I had a similar issue while working on a project; specifying the relationship on both the parent and the child solved my issue:
User:
module.exports = (sequelize, DataTypes) => {
class User extends Model {
static associate(models) {
User.hasMany(models.Recipe, {as: 'recipes', foreignKey:'userId'})
}
};
User.init({
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false
},
password: {
type: DataTypes.STRING,
allowNull: false
}
}, {
sequelize,
modelName: 'User',
});
return User;
};
Recipe:
module.exports = (sequelize, DataTypes) => {
class Recipe extends Model {
static associate(models) {
Recipe.belongsTo(models.User, { foreignKey: 'userId' })
}
};
Recipe.init({
title: {
type: DataTypes.STRING,
allowNull: false
},
ingredients: {
type: DataTypes.STRING,
allowNull: false
},
direction: {
type: DataTypes.STRING,
allowNull: false
}
}, {
sequelize,
modelName: 'Recipe',
});
return Recipe;
};

Sequelize bulk insert with associations

I'm trying to bulk insert with associations,
I have this 'Song' model which have one to many relationships with 'Genre' and 'Language' defined with the migrations CLI.
Song:
module.exports = (sequelize, DataTypes) => {
class Song extends Model {
static associate(models) {
// define association here
Song.hasMany(models["Language"])
Song.hasMany(models["Genre"])
}
};
Song.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: DataTypes.STRING,
energy: {type: DataTypes.FLOAT, allowNull: false},
valence: {type: DataTypes.FLOAT, allowNull: false}
}, {
sequelize,
modelName: 'Song',
timestamps: true
});
return Song;
};
Language:
module.exports = (sequelize, DataTypes) => {
class Language extends Model {
static associate(models) {
// define association here
models["Language"].belongsTo(models["Song"])
}
};
Language.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: DataTypes.STRING
}, {
sequelize,
modelName: 'Language',
indexes: [{unique: true, fields: ['name']}]
});
return Language;
};
Genre:
module.exports = (sequelize, DataTypes) => {
class Genre 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["Genre"].belongsTo(models["Song"])
}
};
Genre.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: DataTypes.STRING
}, {
sequelize,
modelName: 'Genre',
indexes: [{unique: true, fields: ['name']}]
});
return Genre;
};
I'm trying to bulk insert songs with languages and genres like this:
Song.bulkCreate(songs, {
include: [Genre,Language]
}).then(() => {
const result = {
status: "ok",
message: "Upload Successfully!",
}
res.json(result);
});
each song in the songs array is structured like this:
{
name: "abc",
genres: [{name: "abc"}],
languages: [{name: "English"}],
energy: 1,
valence: 1
}
I'm ending up with a full songs table but genres and languages are empty
What am I doing wrong?
Thanks.
Just in case anyone else got here from a search, starting from version 5.14
Sequelize added the option to use include option in bulkCreate as follows:
await Song.bulkCreate(songsRecordsToCreate, {
include: [Genre,Language]
})
Edit 2nd Feb 2023
As none answered above, as of v5.14.0 the include option is now available on bulkInsert.
Unfortunately bulkCreate does not support include option like create do.
You should use create in a cycle inside a transaction.
const transaction = ...
for (const song of songs) {
await Song.create(song, {
include: [Genre,Language]
}, { transaction })
}
await transaction.commit()
or you can use Promise.all to avoid using for.

Sequelize: <table> is not associated to <Table>

I'm getting images is not associated to product! error while binding the association of the model.
ProductImages is associated to Product and ProductImages is associated to Images model. So, i need to render images property into products collection by assigning to it.
The model that i'm trying to bind is as below.
products.model.ts
const Product = SQLize.define('product', {
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }
product_title: { type: new DataTypes.STRING(255) },
vendor_id: { type: DataTypes.INTEGER }
});
Product.hasMany(ProductImages, {foreignKey: 'product_id', targetKey: 'id', as :'product_img_refs'})
export { Product };
product-images.model.ts
const ProductImages = SQLize.define('product_images', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, },
product_id: { type: DataTypes.INTEGER },
product_image_id: { type: DataTypes.INTEGER }
img_type_id: { type: DataTypes.INTEGER }
});
ProductImages.belongsTo(ImagesModel, {foreignKey: 'product_image_id', targetKey: 'id', as:'product_images' })
export {ProductImages}
images.model.ts:
const ImagesModel = SQLize.define('images', {
id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, },
img_url: { type: DataTypes.STRING }
});
export { ImagesModel }
Below is the repository file on which i have performed the SQLize operation.
public async getProductData() {
var prodData = Product.findAll({
include: [
{ model: Vendor, as: 'vendor' },
{ model: ProductImages, as: 'product_img_refs' }
{ model: ImagesModel, as: 'product_images' }
]
});
return prodData;
}
=> Sample product_images table records.
=> Sample images table records.
=> DB Schema for more visualisation.
=> I have checked this answer but it is not relevant to my model as i have three models with different association.
Instead of both a hasMany and a belongsTo relationship, create a many-to-many relationship on Product to Images and also one from Images to Product.
You can extend the auto-generated table (with ProductId and ImageId columns) by passing the name of a model.
const ProductImages = SQLize.define('ProductImages', {
// ...
});
Product.belongsToMany(ImagesModel, { through: ProductImages });
ImagesModel.belongsToMany(Product, { through: ProductImages });
You can now do:
await Product.getImages();
await Images.getProducts();
Or use the include option while querying. There are examples in the documentation here. It'll be something like:
await Product.findAll({
include: ImagesModel,
});
// It will be nested as such:
// {
// fields from product
// Images: {
// fields from image
// ProductImages: {
// fields from the 'through' table
// }
// }
// }

Sequelize - include all association filtered results

I have a a model called Post and Country. When I filter by a particular country Id, it returns correctly filtered posts but only returns that specific country in the response and discards all the other countries associated with the Post. How can I include and retain all the countries associated with the post in the response?
Post.js
class Post extends Sequelize.Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
number: {
type: Sequelize.INTEGER,
validate: {
isInt: true
}
},
}
);
static associate(models) {
this.myAssociation = this.belongsToMany(models.Country,
{through: "CountriesImpacted", foreignKey: "id"});
}
}
module.exports = Incident;
Country.js
class Country extends Sequelize.Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
primaryKey: true,
unique: true
}
}, {sequelize, modelName: 'country', tableName: 'Countries'}
)
}
}
module.exports = Country;
routes.js
Post.findAndCountAll({
attributes: { exclude: ["createdAt", "updatedAt"] },
order: [Country, "name", "asc"],
include: [{
model: Country,
where: { id: country_ids_from_request }
}],
where: filters,
distinct: true,
offset: offset,
limit: limit
})
example output
{
'0': {
id: 1,
number: 1203021,
countries: [
{
id: 6,
name: 'Australia',
CountriesImpacted: {
id: 1,
countryId: 6
}
},
{
id: 7,
name: 'New Zealand',
CountriesImpacted: {
id: 1,
countryId: 7
}
}
]
}
}
when I filter by country id 6, the repsonse will discard New Zealand...
It requires a sub-query to fetch the eligible Post model. It will look like following
return Post.findAll({
include: [
{
model: Country
}
],
where: {
id : {
[Sequelize.Op.in] : [Sequelize.literal(`(SELECT posts.id FROM posts INNER JOIN countries ON countries.postId = posts.id WHERE ${COUNTRY_ID} IN (countries.id))`)] // Subquery...
}
}
})
Here i assume following
Your Post schema name is posts.
Your Country schema name is countries.
Country has a postsId foreign key REFERENCES TO Post.
If you are not comfortable with having sub-query inside your project while using Sequelize (ORM), then you have to execute two query, first fetching all the eligible Posts then fetch desired for those Posts.

Sequelize Model Relationships

I have 2 models and what I am trying to do is when a match is created, it will automatically create a match report. here is the code:
Match.js
const Sequelize = require('sequelize');
const db = require('../config/database');
const Match = db.define('matches',{
id: {
type: Sequelize.INTEGER(10).UNSIGNED,
autoIncrement: true,
primaryKey: true
},
type: {
type: Sequelize.STRING,
defaultValue: 'main' //match type either Main or Sub
},
game_grp: {
type: Sequelize.SMALLINT(6),
defaultValue: null // belongs to main match side bet.
},
sub_type: {
type: Sequelize.STRING,
defaultValue: null //values: (other = 1stBlood,F10k), (main = MatchWinner), (handicap = Match Handicap)
},
name: {
type: Sequelize.STRING,
defaultValue: null
},
league_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'League', //leagues has many matches
key: 'id'
}
},
},
);
Match.associate = models => {
Match.hasMany(models.MatchReport, {
foreignKey: 'id'
});
};
module.exports = Match;
MatchReport.js
const Sequelize = require('sequelize');
const db = require('../config/database');
const MatchReport = db.define('match_reports',
{
id: {
type: Sequelize.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
references: {
model: 'Match', //MatchReport's ID belongs to matches'
key: 'id'
}
},
league_id: {
type: Sequelize.INTEGER,
defaultValue: 0,
references: {
model: 'Match',
key: 'league_id'
}
},
name: {
type: Sequelize.STRING,
defaultValue: null
},
status: {
type: Sequelize.STRING,
defaultValue: null //ongoing,draw,cancelled,open
},
);
module.exports = MatchReport;
I'm new to node.js and sequelize.js model relationships so it's quite hard to understand some of the documentation's details.
Any ideas on how to deal with this? TYIA
First, you have a problem with MatchReport definition:
It needs to have its own id as PK and not as FQ.
MatchReport.league_id is referencing to Match.league_id, so if you'll point to a Match instance it's redundant, right? So, let's point to Match instance instead.
Define your MatchReport as follows:
const Sequelize = require('sequelize');
const db = require('../config/database');
const MatchReport = db.define('match_reports',
{
id: {
type: Sequelize.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
match_id: {
type: Sequelize.INTEGER(10).UNSIGNED,
references: {
model: 'Match',
key: 'id'
}
},
name: {
type: Sequelize.STRING,
defaultValue: null
},
status: {
type: Sequelize.STRING,
defaultValue: null //ongoing,draw,cancelled,open
},
);
module.exports = MatchReport;
Now, there are some options to create MatchReport instance when a Match is created.
1) Put the logic inside a function that creates a match.
async function createMatch(match) {
// match is the object you want to insert.
var newMatch = await Match.create(data);
var newMatchReport = await MatchReport.create({
match_id: newMatch.id,
name: ...,
status: ...., //
});
}
2) Use the include option of sequelize.
async function createMatch(match) {
// match is the object you want to insert.
var newMatch = await Match.create(data, {
include: [{ model: MatchReport}]
});
}
Pay attention to the following points as well:
1) When creating an instance of a model the whole instance returns and not only the data. The data itself could be found under dataValues object.
2) You need to apply the associations between the models as follows:
In your Match model:
let MatchReport = require('./MatchReport');
Match.hasOne(MatchReport);
In your MatchReport model:
let Match = require('./Match');
MatchReport.belongsTo(Match);

Resources