Reputation: 1914
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.
Upvotes: 0
Views: 1514
Reputation: 84697
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.
Upvotes: 1