GraphQl/Sequalize combine multiple hasMany/BelongsToMany in single list - graphql

I'm currently working on a project where I have players playing games against each-other in a 1v1 or 2v2 concept.
So I created the following 2 sequelize classes:
player.js
export class Player extends Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
gender: DataTypes.STRING,
firstName: DataTypes.STRING,
lastName: DataTypes.STRING
},
{
sequelize
}
);
}
// Associations
static associate() {
this.games1 = this.hasMany(Game, { as: 'player1_team1', foreignKey: 'player1Team1Id' });
this.games2 = this.hasMany(Game, { as: 'player1_team2', foreignKey: 'player1Team2Id' });
this.games3 = this.hasMany(Game, { as: 'player2_team1', foreignKey: 'player2Team1Id' });
this.games4 = this.hasMany(Game, { as: 'player2_team2', foreignKey: 'player2Team2Id' });
}
}
game.js
export class Game extends Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
playedAt: DataTypes.DATE,
set1_team1: {
type: DataTypes.INTEGER,
allowNull: true
},
set1_team2: {
type: DataTypes.INTEGER,
allowNull: true
},
set2_team1: {
type: DataTypes.INTEGER,
allowNull: true
},
set2_team2: {
type: DataTypes.INTEGER,
allowNull: true
},
set3_team1: {
type: DataTypes.INTEGER,
allowNull: true
},
set3_team2: {
type: DataTypes.INTEGER,
allowNull: true
}
},
{
sequelize
}
);
}
// Associations
static associate() {
this.player1_team1 = this.belongsTo(Player, {
as: 'player1_team1',
foreignKey: 'player1Team1Id'
});
this.player1_team2 = this.belongsTo(Player, {
as: 'player1_team2',
foreignKey: 'player1Team2Id'
});
this.player2_team1 = this.belongsTo(Player, {
as: 'player2_team1',
foreignKey: 'player2Team1Id'
});
this.player2_team2 = this.belongsTo(Player, {
as: 'player2_team2',
foreignKey: 'player2Team2Id'
});
}
}
Then the following scheme for GraphQl
gql`
type Player {
id: ID!
firstName: String!
lastName: String!
games: [Game]
}
type Game {
id: ID!
player1_team1: Player!
player1_team2: Player!
player2_team1: Player!
player2_team2: Player!
}
type Query {
player(id: ID!): Player
}
`
Now I'm trying to have when you query for the Player that you get all the games whether he is player1_team1, player2_team2...
But i'm a bit blocked on figuring out how to do this :/
I tried adding getGames() that combined the 4 arrays to my class, but I didn't find how to call this method
getGames() {
return [...this.games1, ...this.games2, ...this.games3, ...this.games4];
}
I tried searching for a way that you could query with a alias that combines the 4 childs (player1_team1...) but with no success (still new in GraphQl)
Could anyone help me out?

Game is a class. Calling new Game or a static method like Game.create generates an instance of the class. So we have
const Game = require('the location of the model')
const game = new Game()
In this example, static methods will be available on the Game variable because they apply to the class, not an instance of that class. Likewise, non-static methods you define inside the class will be available on instance and not the class (i.e. the game variable).
The hasMany method creates an association between the two models and returns an instance of the HasMany class. By writing this:
this.games1 = this.hasMany(Game, { ... })
you are setting the games1 static property because you are doing this inside a static method. As a result, the property is available on the class, not the instance. Saving the resulting association object to a static property can be helpful, not it's also not necessary. You could just as easily do:
this.hasMany(Game, { ... })
The important bit is that by calling hasMany, you are actually creating a getter on instances of the class. In this case, the 4 getters will be named getPlayer1_team1, getPlayer2_team1, getPlayer1_team2 and getPlayer2_team2 based on the alias you provided (the as parameter).
So you can add a method like:
async getGames() {
const [games1, games2, games3, games4] = await Promise.all([
this.getPlayer1_team1(),
this.getPlayer1_team2(),
this.getPlayer2_team1(),
this.getPlayer2_team2(),
])
return [...this.games1, ...this.games2, ...this.games3, ...this.games4]
}
and then call it from some instance:
const player = await Player.findByPk(1)
const games = await Player.getGames()
If your schema exposes a games field on the Player type, you can just do this in the resolver for the field:
function resolve(parent, args, context, info) {
return parent.getGames()
}
Alternatively...
You can lazy load the associated models and get the games when you fetch the player. This is generally more efficient than fetching the player and then fetching the games. So when fetching the player, you can do something like:
const player = await Player.findByPk(1, {
include: [
{ as: 'player1_team1', model: Game },
{ as: 'player1_team2', model: Game },
{ as: 'player2_team1', model: Game },
{ as: 'player2_team2', model: Game },
]
})
Note: it's important to include the same as value that you used when you defined the association. The resulting player variable will now have 4 properties on it for the associated games (player1_team1, player1_team2, etc.).
Provided you lazy load the associated Games like this when fetching instances of Player, you can now do something like this to resolve your games field in GraphQL:
function resolve(parent, args, context, info) {
return [
...parent.player1_team1,
...parent.player1_team2,
...parent.player2_team1,
...parent.player2_team2,
]
}

Related

Sequlize combining has many association in single field

For the project I'm working on a player has multiple games. But these are always needed in a combined way.
So I was wondering on how i could make it so I can place it in my graphql scheme as a signle item called games.
I've did something similar for the fullName but I couldn't find a way to make a combined association
#Table({
timestamps: true
})
export class Player extends Model<Player> {
#Column({ unique: 'compositeIndex' })
firstName: string;
#Column({ unique: 'compositeIndex' })
lastName: string;
#Column({ unique: 'compositeIndex' })
memberId: string;
// Assicioations
#HasMany(() => Game, { as: 'player1-team1', foreignKey: 'player1Team1Id' })
games1?: Game[];
#HasMany(() => Game, { as: 'player1-team2', foreignKey: 'player1Team2Id' })
games2?: Game[];
#HasMany(() => Game, { as: 'player2-team1', foreignKey: 'player2Team1Id' })
games3?: Game[];
#HasMany(() => Game, { as: 'player2-team2', foreignKey: 'player2Team2Id' })
games4?: Game[];
#Column(DataType.VIRTUAL(DataType.STRING, ['firstName', 'lastName']))
get fullName(this: Player): string {
return `${this.firstName || ''} ${this.lastName || ''}`.trim();
}
get games() {
return [...this.games1, ...this.games2, ...this.games3, ...this.games4];
}
}
Current Scheme object
const playerType = new GraphQLObjectType({
name: 'Player',
description: 'A Player',
fields: Object.assign(attributeFields(sequelizeInstance.models.Player), {
games: {
type: new GraphQLList(gameType),
resolve: resolver(/* Some magic here? */),
}
})
});
return new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
player: {
type: playerType,
args: {
id: {
description: 'id of the user',
type: new GraphQLNonNull(GraphQLID)
}
},
resolve: resolver(Player)
}
}
})
});
};
Found a 'solution' to my problem
I've made the "error" that I've made 4 foreign keys. by creating a many to many releation i was able to get everything working as I wanted to.
But I'm still curious on how one would do this ...

Is it possible to do a multi tenancy with Graphql and Sequelize?

I have a rather tricky question about GraphQl and multi-tenancy.
Let's assume there are 3 tables, OWNER, HOUSE and TENANTS. I will describe them in Sequelize and GraphQl pseudo code:
Owner table (has multiple houses and multiple tenants)
const OWNER = sequelize.define('owner', {
ownerId: type: Sequelize.INTEGER,
name: type: Sequelize.STRING
}
OWNER.associate = models => {
models.owner.hasMany(models.house, {foreignKey: {name: 'ownerId', field: 'ownerId'}})
models.owner.hasMany(models.tenant, {foreignKey: {name: 'ownerId', field: 'ownerId'}})
}
House table (belongs to owner and has multiple tenants)
const HOUSE = sequelize.define('house', {
houseId: type: Sequelize.INTEGER,
ownerId: type: Sequelize.INTEGER,
name: type: Sequelize.STRING
}
HOUSE.associate = models => {
models.house.belongsTo(models.owner, {foreignKey: {name: 'ownerId', field: 'ownerId'}})
models.house.hasMany(models.tenant, {foreignKey: {name: 'houseId', field: 'houseId'}})
}
Tenant table (belongs to owner and house)
const TENANT = sequelize.define('tenant', {
tenantId: type: Sequelize.INTEGER,
ownerId: type: Sequelize.INTEGER,
houseId: type: Sequelize.INTEGER,
name: type: Sequelize.STRING
}
TENANT.associate = models => {
models.tenant.belongsTo(models.owner, {foreignKey: {name: 'ownerId', field: 'ownerId'}})
models.tenant.belongsTo(models.house, {foreignKey: {name: 'houseId', field: 'houseId'}})
}
The owner graphql object
const OwnerType = new GraphQLObjectType({
name: 'Owner',
fields: () => ({
ownerId: { type: GraphQLInt },
name: { type: GraphQLString },
houses: {
type: GraphQLList(HouseType),
resolve(owner) {
return owner.getHouse()
}
},
houseById: {
type: HouseType,
args: <args is not defined>
resolve(owner) {
return <???>
}
},
})
})
Here are a few simple GraphQL queries:
ownerById = {
type: OwnerType,
args: {
ownerId: { type: GraphQLInt },
},
resolve(parents, args){
return models.owner.findOne({ where: args })
}
}
houses = {
type: GraphQLList(HouseType),
resolve(parents, args){
return models.house.findAll()
}
}
houseById = {
type: HouseType,
args: {
houseId: { type: GraphQLInt },
},
resolve(parents, args){
return models.house.findOne({ where: args })
}
}
tenants = {
type: GraphQLList(TenantType),
resolve(parents, args){
return models.tenant.findAll()
}
}
These client queries work:
{
ownerById(ownerId: 1) {
ownerId
name
house {
houseId
name
}
}
}
{
houseById(houseId: 2) {
houseId
name
tenant {
tenantId
name
}
}
}
What I need to make multi-tenancy to work is something like that:
{
ownerById(ownerId: 1) {
ownerId
name
houseById(houseId: 2) {
houseId
name
tenant {
tenantId
name
}
}
}
}
Is there a way to archive this or is that out of scope what GraphQl can do?
If yes, how would the graphql object houseById query look like?
Thanks in advance.
Unless I'm missing something, it seems like your resolver for houseById would not be that different from the resolver for the houses field on the same type.
houseById: {
type: HouseType,
args: {
houseId: { type: GraphQLInt },
},
async resolve(owner, { houseId }) {
const houses = await owner.getHouses({ where: { id: houseId } })
return houses[0]
}
},
For a HasMany association, the getter for the target model resolves to an array of instances. So we need to grab that array first and then return just the first item in it, since our field represents a single object and not a list. If you don't want to use async/await, you can also do:
return owner.getHouses({ where: { id: houseId } })
.then(houses => houses[0])
It would also be worth mentioning that this sort of pattern for a schema defies convention. Rather than having a houses field, a houseById field, a houseBySomeOtherArg field, etc., consider exposing a single houses field with one or more arguments like id, name or whatever filter criteria you want to provide. Your field can then just filter the houses based on whatever arguments are passed in, or return all results if no filter arguments were provided.

Is it possible to fetch data from multiple tables using GraphQLList

In GraphQL we can write the object type in GraphQLList and fetch all the fields. I am using Association and it is joining the two tables but I am unable to fetch the field of both the tables. It only takes the fields what I have written in GraphQLList.As I want the list of data.
Here is the code
films table:
module.exports =(sequelize, DataTypes) => {
const films = sequelize.define(
'films',
{
id:{
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
},
name: {
type: DataTypes.STRING,
},
},
);
films.associate = (models) => {
films.hasMany(models.movie_stream, {
foreignKey: 'movie_id',
});
};
return films;
}
movie_stream table:
module.exports = (sequelize, DataTypes) => {
const movie_streams = sequelize.define('movie_streams', {
id:{
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
},
movie_id: {
type: DataTypes.STRING,
foreignKey: "movie_id",
},
});
movie_streams.associate = (models) => {
movie_streams.hasMany(models.films, {
foreignKey: 'id',
});
};
return movie_streams;
};
Schema file:
movieList:{
type: new GraphQLList(Films),
resolve: (parent,args)=>{
return newdb.films.findAll({attributes:['id','name','permalink'],
where: {content_category_value:parent.id },
include: [{
model:newdb.movie_stream,
attributes:['id','movie_id'],
}],
}).then(data=>{
return data;
})
}
Can I write here type: new GraphQLList(Films, MovieStream)??
I have tried but It does not work. Please give me some idea how do I fetch the fields of both the table???
There's two main ways of achieving this in GraphQL: unions and interfaces.
An interface is where two or more object types in your GraphQL schema share certain fields (characteristics). For example you might have a Product interface for all the items in your shop, where every product has a price, barcode, and shelfLocation. All your products, such as Shampoo, Bread, LawnChair would then implement this interface.
interface Product {
price: Float
barcode: Int
shelfLocation: ShelfLocation
}
type Bread implements Product {
price: Float
barcode: Int
shelfLocation: ShelfLocation
brand: String
numberOfSlices: Int
calories: Float
bestBefore: Date
}
extend type Query {
searchProducts(phrase: String!): [Product!]
}
A union is where you declare that something can return more than one object type, but those types don't have to have any properties in common.
type Shark {
name: String
numberOfTeeth: Int
}
type Shoe {
brand: String
size: String
}
union SharkOrShoe = Shark | Shoe
extend type Query {
searchSharksAndShoes(phrase: String!): [SharkOrShoe!]
}
In both cases you can query type specific fields using fragments or inline fragments:
query {
searchProducts(phrase: "tasty") {
# shared fields
__typename
price
barcode
shelfLocation { aisle, position }
# type specific fields
... on Bread { brand }
...breadFrag
}
searchSharksAndShoes(phrase: "sleek") {
# only the introspection fields are shared in a union
__typename
# type specific fields
... on Shark { name, numberOfTeeth }
...shoeFrag
}
}
fragment breadFrag on Bread {
barcode
bestBefore
}
fragment shoeFrag on Shoe {
brand
size
}
You can learn more about this in the GraphQL schema documentation and read about GraphQLInterfaceType and GraphQLUnionType in the GraphQL.js documentation.

how to set many-to-many relation in graphql mutation?

I may be missing something, but can not find any information on Apollo docs about the way to set a many-to-many relation when creating a new entry.
When the relation is one-to-many it is as simple as setting the ID of the one-side of the relationship in the many-side object.
But let's pretend I am working with Books and Authors, how would I write a graphql query that creates a Book for one (or many?) Authors?
This should probably happen at the API layer on the GraphQL server (i.e. schema). For many-to-many relationships, you should have a "join" type to denote the BookAuthor many-to-many relationship, and then add an entry to that join type.
Essentially then you'll have a type called Book, another called Author, and finally one more called BookAuthor. And you can add a few mutations to be able to manage that relationship. Perhaps...
addToBookAuthorConnection
updateBookAuthorConnection
removeFromBookAuthorConnection
This is a conventional setup using a Relay-spec compliant API. You can read more about how to structure your API for many-to-many relationships here.
Then, you only need to call the addToBookAuthorConnection mutation from Apollo instead to be able to add to that many-to-many connection on your frontend.
Hope this helps!
If u r using apollo graph server with one to many relations then connectors.js, resolvers.js and schema.js files as given formats
schema.js
const typeDefinitions = `
type Author {
authorId: Int
firstName: String
lastName: String
posts: [Post]
}
type Post {
postId: Int
title: String
text: String
views: Int
author: Author
}
input postInput{
title: String
text: String
views: Int
}
type Query {
author(firstName: String, lastName: String): [Author]
posts(postId: Int, title: String, text: String, views: Int): [Post]
}
type Mutation {
createAuthor(firstName: String, lastName: String, posts:[postInput]): Author
updateAuthor(authorId: Int, firstName: String, lastName: String, posts:[postInput]): String
}
schema {
query: Query
mutation:Mutation
}
`;
export default [typeDefinitions];
resolvers.js
import { Author } from './connectors';
import { Post } from './connectors';
const resolvers = {
Query: {
author(_, args) {
return Author.findAll({ where: args });
},
posts(_, args) {
return Post.findAll({ where: args });
}
},
Mutation: {
createAuthor(_, args) {
console.log(args)
return Author.create(args, {
include: [{
model: Post,
}]
});
},
updateAuthor(_, args) {
var updateProfile = { title: "name here" };
console.log(args.authorId)
var filter = {
where: {
authorId: args.authorId
},
include: [
{ model: Post }
]
};
Author.findOne(filter).then(function (product) {
Author.update(args, { where: { authorId: args.authorId } }).then(function (result) {
product.posts[0].updateAttributes(args.posts[0]).then(function (result) {
//return result;
})
});
})
return "updated";
},
},
Author: {
posts(author) {
return author.getPosts();
},
},
Post: {
author(post) {
return post.getAuthor();
},
},
};
export default resolvers;
connectors.js
import rp from 'request-promise';
var Sequelize = require('sequelize');
var db = new Sequelize('test', 'postgres', 'postgres', {
host: '192.168.1.168',
dialect: 'postgres',
pool: {
max: 5,
min: 0,
idle: 10000
}
});
const AuthorModel = db.define('author', {
authorId: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true, field: "author_id" },
firstName: { type: Sequelize.STRING, field: "first_name" },
lastName: { type: Sequelize.STRING, field: "last_name" },
},{
freezeTableName: false,
timestamps: false,
underscored: false,
tableName: "author"
});
const PostModel = db.define('post', {
postId: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true, field: "post_id" },
text: { type: Sequelize.STRING },
title: { type: Sequelize.STRING },
views: { type: Sequelize.INTEGER },
},{
freezeTableName: false,
timestamps: false,
underscored: false,
tableName: "post"
});
AuthorModel.hasMany(PostModel, {
foreignKey: 'author_id'
});
PostModel.belongsTo(AuthorModel, {
foreignKey: 'author_id'
});
const Author = db.models.author;
const Post = db.models.post;
export { Author, Post };

How to flatten an object in GraphQL?

I have a nested javascript class (see below) that is exposed through my GraphQL server. Can I write a GQL schema that exposes this complex structure as a single object? (aka flattened).
The Nested Object
interface Promotion {
id
type
data: PromotionType1 | PromotionType2
}
interface PromotionType1 {
a
b
}
interface PromotionType2 {
c
d
}
The desired GQL query to access the Object
I want to write a GQL schema so that I can query this object as follows:
promotion(id: "123") {
id
type
... on PromotionType1 {
a
b
}
... on PromotionType2 {
c
d
}
}
Is this possible with GQL?
You can use GraphQLUnitType and GraphQLInterfaceType to be able to make that GraphQL query to access the object, if you restructure your nested object. It seems you intended to use inheritance while designing promotion types and ended up having the subtypes as a field in parent type. Instead the structure should be like:
interface Promotion {
id
type
}
interface PromotionType1 extends Promotion {
a
b
}
interface PromotionType2 extends Promotion {
c
d
}
Promotion is the base type. We can have it as GraphQLInterfaceType:
const PromotionType = new GraphQLInterfaceType({
name: 'PromotionInterface',
fields: {
id: { type: GraphQLID },
type: { type: GraphQLString }
}
});
You need instances of PromotionType1 and PromotionType2. So, these can be GraphQLObjectType.
const PromotionType1 = new GraphQLObjectType({
name: 'PromotionType1',
interfaces: [ PromotionType ],
fields: {
id: { type: GraphQLID },
type: { type: GraphQLString },
a: { type: GraphQLString },
b: { type: GraphQLString },
},
isTypeOf: value => value instanceof PromotionType1
});
const PromotionType2 = new GraphQLObjectType({
name: 'PromotionType2',
interfaces: [ PromotionType ],
fields: {
id: { type: GraphQLID },
type: { type: GraphQLString },
c: { type: GraphQLString },
d: { type: GraphQLString },
},
isTypeOf: value => value instanceof PromotionType2
});
If your have JS class Promotion1 for GraphQL type PromotionType1 and Promotion2 for PromotionType2, the GraphQLObjectType for exposing promotion data will be like:
var Promotion = new GraphQLUnionType({
name: 'Promotion',
types: [ PromotionType1, PromotionType2 ],
resolveType(value) {
if (value instanceof Promotion1) {
return PromotionType1;
}
if (value instanceof Promotion2) {
return PromotionType2;
}
}
});
You can then query promotion data with:
promotion(id: "123") {
id,
type,
... on PromotionType1 {
a,
b,
}
... on PromotionType2 {
c,
d,
}
}
You can check out this example.
One possible solution is to flatten the object structure in your resolver. This would avoid the need to do anything complex in your GQL schema.

Resources