GraphQL Schema - how to represent (and query) a hierarchical structure - graphql

I am using GraphQL to build a "proof of concept" service.
One of the objects I am modelling in the Schema, is an Organisation's structure. Typically, an organisational structure is hierarchical, and different nodes could have different number of child nodes etc.
Typically, an org chart looks a bit like this:
CEO
|
----------------------------------
| | |
Tech Team Middle Management ...
I a sure this is a problem that has been solved several times, in GraphQL applications - however, I can't seem to find any documentation that shows how to represent a hierarchical structure (such as an org. chart), using GraphQL
type Organisation {
# Root Node here ...
}
How can a hierarchical structure be represented in a GraphQL Schema?

A type can refer to itself. In your case an employee can have a supervisor (except for the CEO who has none) and can also optionally have staff.
type Employee {
id: ID!
firstName: String!
lastName: String!
supervisor: Employee
staff: [Employee]
}

Related

How to sustain potentially infinite Graphql queries?

There's one thing I have never understood about Graphql. I think I'll paint by problem by giving some examples.
Let's say I have an Account type and a Chat type. An Account can have multiple Chats associated with it, but a Chat can only be associated with a single Account. The schema might look something like this:
type Account {
id: String!
username: String!
chats: [Chat!]!
}
type Chat {
id: String!
text: String!
account: Account!
}
Let's say I am exposing the following queries:
type Query {
account(id: String!): Account
accounts: [Account!]!
}
Now, I will query my schema:
accounts {
id
username
chats {
id
text
}
}
This is a pretty straigthforward query - I simply what to grab all the chats by all the accounts.
But what if I have a deeper query like this:
accounts {
id
username
chats {
id
text
account {
id
username
chats {
id
text
}
}
}
}
I know that in reality, querying this data would be ridiculous, but still, it should be possible, right?
Now let's switch our view from the consumer of the API to the actual developer of the API.
How is the graphql developer meant to sustain these, potentially, infinite queries? Does Graphql resolve these automatically (it doesn't seem to)? Or am I just missing something out?
Thanks for any help.
How is the graphql developer meant to sustain these, potentially, infinite queries?
Not possible in having "infinite" nested queries.
GraphQL schema should be acyclic - meaning the relationships between types should be unidirectional.
Just a rule that the schema should be based on the query patterns in the client application.
So having a nested account within the chat would simply mean the use case and sequence are not properly defined.

Nested mutation GraphQL

I'm using AWS Appsync and Amplify.
A snippet of my GraphQL schema look like this:
type Recipe
#model
#auth(rules: [{allow: owner}])
{
id: ID!
title: String!
key: String!
courses: [Course!]!
}
type Course
#model
#auth(rules: [{allow: owner}])
{
id: ID!
name: String!
}
On amplify push it creates the DynamoDB tables Recipe and Course
After reading many tutorials I still don't get it how to add a recipe in GraphiQL.
How can i insert a new Recipe that has a reference to a course and avoid duplicates in the Courses table?
To create multiples Recipe referencing the same Course without duplicates in the Course table, you need to design a many-to-many relationship.
So far the relationship you have designed is not enough for AppSync to understand, you are missing #connection attributes. You can read this answer on github to have an explanation of how to design this many-to-many relation in AppSync
After designing the relation, you will use a mutation to insert data, and it's likely that AppSync will generate the mutation code for you (if not, use amplify codegen in the console). You will then be able to create data.
Since you use DynamoDB with multiple tables (default mode for amplify / AppSync), you will have to either :
Call multiple mutations in a row
Use a custom resolver, as described in this SO answer

How to query Apollo GraphQL server with a specific context?

I am writing an Apollo GraphQL API that returns product information from various brands. A simplified version of the schema looks like this:
type Query {
products: [Product]!
}
type Product {
name: String!
brand: String!
}
I want to be able to query products from a specific brand. Normally this would be simple to achieve by adding a brand argument to the Product object:
type Query {
products(brand: String!): [Product]!
}
However, I have multiple GraphQL clients in different apps and each is associated with a specific brand so it seems redundant to always pass the same brand argument in every query. I also have many other objects in my schema (orders, transactions, etc.) that are specific to a brand and would require a brand argument.
Furthermore, my resolvers need to query a different API depending on the brand so even objects in my schema such as User, which are conceptually unrelated to a brand, would potentially need a brand argument so that the resolver knows which API to fetch from.
Is there a way to set the brand context for each client and have this context received by the server? Or maybe there is a better way to achieve this brand separation?
I would probably make Brand be a first-class type in your GraphQL query. That doesn't save you from having to qualify many of the queries you describe by a specific brand, but it at least gives you a common place to start from. Then you'd wind up with an API somewhat like:
type Query {
brand(name: String!): Brand
allProducts: [Product!]!
}
type Brand {
name: String!
products: [Product!]!
# users: [User!]!
}
type Product {
name: String!
brand: Brand! # typical, but not important to your question
}
If the differences between kinds of brands are visible at the API layer, you also could consider using a GraphQL interface to describe the set of fields that all brands have, but actually return a more specific type from the resolver.
The way you describe your application, it could also make sense to run one copy of the service for each brand, each with a different GraphQL endpoint. That would let you straightforwardly parameterize the per-brand internal object configuration and make the "current brand" be process-global context. The big constraints here are that, at a GraphQL level, one brand's objects can never refer to another, and if you have a lot of brands, you need some good way to run a lot of servers.

Graphql data modeling: extending types and interfaces

This is a very basic question but how do you call an extended type or interface?
All the documentations points to using extend type Person to add fields based on Person.
I would expect it to work like this
Employee extend type Person {
salary: Int!
}
But the documentation suggests it's like this:
extend type Person{
salary: Int!
}
So, how do I query for an Employee salary? What if there are multiple extensions of Person, e.g. Employee and Renter? I think I might be hampered by traditional thinking but I would expect the extension to result in something named and queryable.
The extend keyword is effectively used to modify an existing type within a schema. This is most commonly used in two scenarios:
1. Concatenating multiple strings that represent a single schema. You can have your schema broken up across multiple files, divided by domain. You can then do something like:
#base.graphql
type Query {
viewer: User
}
# user.graphql
extend type Query {
users: [User!]!
}
# post.graphql
extend type Query {
post: [Post!]!
}
This results in a schema that's effectively the same as:
type Query {
viewer: User
users: [User!]!
post: [Post!]!
}
2. Extending from a base schema. You might have multiple schemas that build on top of some base schema. Or you might be stitching together remote schemas. In these scenarios, we often want to add fields specific to our new schema that don't exist on the base types. This can be used to implement directives that are missing from the base schema as well:
extend type SomeType #customDirective
The extend keyword can only modify existing types; it is not a vehicle for inheritance. In fact, GraphQL does not support type inheritance. Interfaces provide a level of abstraction over existing types, but types that implement an interface do not inherit any fields from that interface. There's no way to do that, unless you use some library like graphql-s2s.

Is there a good way to combine resolvers for multiple GraphQL objects, preferably using graphql-js?

Suppose you have a GraphQL layer, written on node.js using graphql-js, that communicates with a SQL database. Suppose you have the following simple types and fields:
Store
A single brick-and-mortar location for a chain of grocery stores.
Fields:
id: GraphQLID
region: StoreRegion
employees: GraphQLList(Employee)
StoreRegion
A GraphQLEnumType containing the list of regions into which the chain divides its stores.
Values:
NORTHEAST
MIDATLANTIC
SOUTHEAST
...
Employee
Represents a single employee working at a store.
Fields:
id: GraphQLID
name: GraphQLString
salary: GraphQLFloat
Suppose the API exposes a store query that accepts a Region and returns a list of Store objects. Now suppose the client sends this query:
{
store(region: NORTHEAST) {
employees {
name
salary
}
}
}
Hopefully this is all pretty straightforward so far.
So here's my question, and I hope (expect, really) that it's something that has a common solution and I'm just having trouble finding it because my Google-Fu is weak today: is there a good way that can I write the resolvers for these types such that I can wrap up all the requested fields for all the employees from all the returned stores into a single SQL query statement, resulting in one round-trip to the database of the form:
SELECT name,salary FROM employees WHERE id IN (1111, 1133, 2177, ...)
rather than making one request per employee or even one request per store?
This is really a concrete instance of a more general question: is there a good way to combine resolvers to avoid making multiple requests in cases where they could be easily combined?
I'm asking this question in terms of graphql-js because that's what I'm hoping to work with, and since I figure that would allow for more specific answers, but if there's a more implementation-agnostic answer, that would be cool too.
So, basically you are wondering how you can combine multiple resolvers into fewer database queries. This is trying to solve what they call the N+1 query problem. Here’s at least two ways you can solve this.
DataLoader: This is a more general solution and it's created by Facebook. You could use this to batch multiple queries that query a single item of a single type into a single query that queries multiple items of a single type. In your example you would batch all employees into a single DB query and you would still have a separate query for getting the store. Here's a video by Ben Awad that explains DataLoader
JoinMonster: Specifically for SQL. Will do JOINs to make one SQL query per graphql query. Here's a video by Ben Awad explaining JoinMonster

Resources