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

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.

Related

Graphql multiple arguments in field

I'm using GraphQL.
I'm able to pass one argument in a field. But I would like to know how to pass multiple arguments to a field.
This is my code:
GraphlQL Object type: Price availability
const priceAvailability = new GraphQLObjectType({
name: "priceAvailability",
description: "Check price and availability of article",
fields: () => ({
articleID: {
type: GraphQLString
},
priceType:{
type:GraphQLString
},
stockAvailability: {
type: StockAvailabilityType,
resolve(parentValue, args) {
// stuff to get the price and availability
return (data = getStockAvailability.getStockAvailability(
parentValue.isbn, parentValue.omgeving
));
}
}
})
});
The root query
const RootQuery = new GraphQLObjectType({
name: "RootQuery",
fields: () => ({
price: {
type: new GraphQLList(priceAvailability),
args: [{
articleID: {
type: new GraphQLList(GraphQLString),
description:
'List with articles. Example: ["artid1","artid2"]'
},
priceType: {
type: new GraphQLList(GraphQLString) ,
description:
'PriceType. Example: "SalePrice","CurrentPrice"'
}]
},
resolve: function(_, { articleID , priceType}) {
var data = [];
// code to return data here
return data;
}
}
})
});
Schema
module.exports = new GraphQLSchema({
query: RootQuery
});
This is the query I use in GraphiQL to test:
{
query: price(articleID:"ART03903", priceType:"SalePrice" ){
stockAvailability {
QuantityAvailable24hrs
QuantityAvailable48hrs
}
}
}
I can get the articleID via parentValue.articleID, but I have issues with getting parentValue.priceType.
Also GraphiQL tells me that priceType does not exists:
Unknown argument “priceType”. On field “price” of type “RootQuery”
args for a field takes an object instead of an array. Try:
args: {
articleID: {
type: new GraphQLList(GraphQLString),
description: 'List with articles. Example: ["artid1","artid2"]'
},
priceType: {
type: new GraphQLList(GraphQLString) ,
description: 'PriceType. Example: "SalePrice","CurrentPrice"'
},
}

How can GraphQL enable an ID based query at sub fields level?

If an existing service supporting the following GraphQL queries respectively:
query to a person's bank account:
query {
balance(id: "1") {
checking
saving
}
}
result
{
"data": {
"balance": {
"checking": "800",
"saving": "3000"
}
}
}
query to a person's pending order:
query {
pending_order(id: "1") {
books
tickets
}
}
result
{
"data": {
"pending_order": {
"books": "5",
"tickets": "2"
}
}
}
The source code achieving the above functionality is something like this:
module.exports = new GraphQLObjectType({
name: 'Query',
description: 'Queries individual fields by ID',
fields: () => ({
balance: {
type: BalanceType,
description: 'Get balance',
args: {
id: {
description: 'id of the person',
type: GraphQLString
}
},
resolve: (root, { id }) => getBalance(id)
},
pending_order: {
type: OrderType,
description: 'Get the pending orders',
args: {
id: {
description: 'id of the person',
type: GraphQLString
}
},
resolve: (root, { id }) => getPendingOrders(id)
}
})
});
Now, I want to make my GraphQL service schema support person level schema, i.e.,
query {
person (id: "1") {
balance
pending_order
}
}
and get the following results:
{
"data": {
"balance": {
"checking": "800",
"saving": "3000"
}
"pending_order": {
"books": "5",
"tickets": "2"
}
}
}
How can I re-structure the schema, and how can I reuse the existing query service?
EDIT (after reading Daniel Rearden's answer):
Can we optimize the GraphQL service so that we make service call based upon the query? i.e., if the incoming query is
query {
person (id: "1") {
pending_order
}
}
my actually query becomes
person: {
...
resolve: (root, { id }) => Promise.all([
getBalance(id)
]) => ({ balance})
}
You're going to have to define a separate Person type to wrap the balance and pending_order fields.
module.exports = new GraphQLObjectType({
name: 'Person',
fields: () => ({
balance: {
type: BalanceType,
resolve: ({ id }) => getBalance(id)
},
pending_order: {
type: OrderType,
resolve: ({ id }) => getPendingOrders(id)
}
})
});
And you're going to need to add a new field to your Query type:
person: {
type: PersonType,
args: {
id: {
type: GraphQLString
}
},
// We just need to return an object with the id, the resolvers for
// our Person type fields will do the result
resolve: (root, { id }) => ({ id })
}
There's not much you can do to keep things more DRY and reuse your existing code. If you're looking for a way to reduce boilerplate, I would suggest using graphql-tools.

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.

GraphQL mutation without sub section

I want to send graphql mutation request without sub section
mutation _ {
updateCurrentUser(fullName: "Syava", email: "fake#gmail.com")
}
and I am getting
{
"errors": [
{
"message": "Field \"updateCurrentUser\" of type \"User\" must have a sub selection.",
...
}
]
}
add { id } to request works fine but I don't want
Also Schema code
const userType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: { type: new GraphQLNonNull(GraphQLString) },
fullName: { type: GraphQLString },
email: { type: GraphQLString },
}),
});
type: userType,
args: {
fullName: { type: GraphQLString },
email: { type: new GraphQLNonNull(emailType) },
password: { type: GraphQLString },
},
resolve: async (root, { fullName, email, password }, { rootValue }) => {
const user = await User.findById(rootValue.req.user.id);
...
return user;
},
You define the type of the field to be UserType. Even though it's a mutation, it still follows the same rules and behavior as a query. Because UserType is an object type, it requires nested fields.
mutation _ {
updateCurrentUser(fullName: "Syava", email: "fake#gmail.com") {
fullName
email
}
}
// would respond with { fullName: 'Syava', email: 'fake#gmail.com' }
If you don't want the mutation to return a User, you can declare its type to GraphQLBoolean for example -- that's a scalar and doesn't have any nested fields.
{
type: GraphQLBoolean,
args: {
fullName: { type: GraphQLString },
email: { type: new GraphQLNonNull(emailType) },
password: { type: GraphQLString },
},
resolve: async (root, { fullName, email, password }, { rootValue }) => {
const user = await User.findById(rootValue.req.user.id);
user.fullName = fullName;
user.password = password; // or hashed to not store plain text passwords
return user.save(); // assuming save returns boolean; depends on the library you use
}
}
Note that the best practice for mutations in GraphQL APIs is to return a "result" object with multiple fields, such as the mutated object itself (e.g. user), clientMutationId (per Relay spec), and others as needed. This makes it flexible so you could add more data in the future.
updateCurrentUser(fullName: "Syava", email: "fake#gmail.com") {
clientMutationId
user {
...
}
}

Resources