I have some dynamic types that are user generated.
... on Transport
{
type
touchscreen: field(key: "touchscreen") {
id
available
}
}
The field keys are features such as i.e touchscreen, automatic, alarm etc but along with the type, these are also dynamic as they are user generated. For a car it might have touchscreen, alarm etc but for a bike mode of transport it might have soft saddle, or thin wheels as features and obviously it doesn't make much sense to show saddle for type cars (remember these are user generated types).
I want to return a more structured result to avoid a load of nulls. For example something like this would be nice but I am not even sure if its possible or how to construct it as I am not that familiar with GraphQL:
... on transport
... on type == "Car" {
{
type
touchscreen: field(key: "touchscreen") {
id
available
}
electric_windows: field(key: "electric_windows") {
id
available
}
}
... on type == "Bike" {
{
type
electric_motor: field(key: "electric_motor") {
id
available
}
padded_saddle: field(key: "padded_saddle") {
id
available
}
}
Otherwise if I remove ... on type == "Bike" then I will have to include all 4 fields for bikes and cars even though both of those will have 2 null fields. In the real example I might have 10 fields for each so I want to avoid all these nils but not sure what the best approach is.
I'm facing a problem where I need to reference a resolved field on the parent from inside the __resolveType. Unfortunately the field I need to reference did not come as part of the original api response for the parent, but from another field resolver, which I would not have though mattered, but indeed it does, so it is undefined.
But I need these fields (in this example the; obj.barCount and obj.bazCount) to be able to make the following query, so I've hit a dead end. I need them to be available in the resolveType function so that I can use them to determine what type to resolve in case this field is defined.
Here's an example:
The graphql query I wish to be able to make:
{
somethings {
hello
... on HasBarCount {
barCount
}
... on HasBazCount {
bazCount
}
}
}
Schema:
type ExampleWithBarCount implements Something & HasBarCount & Node {
hello: String!
barCount: Int
}
type ExampleWithBazCount implements Something & HasBazCount & Node {
hello: String!
bazCount: Int
}
interface Something {
hello: String!
}
interface HasBarCount {
barCount: Int
}
interface HasBazCount {
bazCount: Int
}
Resolvers:
ExampleWithBarCount: {
barCount: (obj) => {
return myApi.getBars(obj.id).length || 0
}
}
ExampleWithBazCount {
bazCount: (obj) => {
return myApi.getBazs(obj.id).length || 0
}
}
Problem:
Something: {
__resolveType(obj) {
console.log(obj.barCount) // Problem: this is always undefined
console.log(obj.bazCount) // Problem: this is always undefined
if (obj.barCount) {
return 'ExampleWithBarCount';
}
if (obj.bazCount) {
return 'ExampleWithBazCount';
}
return null;
}
}
Any ideas of alternative solutions or what am I missing?
Here's a little more about the use case.
In the database we have a table "entity". This table is very simple and only really important columns are id, parent_id, name. type, and then you can of course attach some additional metadata to it.
Like with "entity", types are created dynamically from within the backend management system, and aftewards you can assign a type to your concrete entity.
The primary purpose of "entity" is to establish a hierarchy / tree of nested entities by parent_id and with different "types" (in the type column of entity). There will be some different meta data, but let's not focus on that.
Note: entity can be named anything, and the type can be anything.
In the API we then have an endpoint where we can get all entities with a specific type (sidenote: and in addition to the single type on an entitiy we also have an endpoint to get all entities by their taxonomy/term).
In the first implementation I modeled the schema by adding all the "known" types I had in my specification from the UX'er during development. The tree of entities could be like eg.
Company (or Organization, ..., Corporation... etc)
Branch (or Region, ..., etc)
Factory (or Building, facility, ..., etc)
Zone (or Room, ..., etc)
But this hierarchy is just one way it could be done. The naming of each might be totally different, and you might move some of them a level up or down or not have them at all, depending on the use case.
Only thing that is set in stone is that they share the same database table, will have the type column/field defined and they may or may not have children. The bottom layer in the hierarchy will not have children, but machines instead. The rest of just diffent metadata, which I think we should ignore for to not complicate this further.
As you can see the hierarchy needs to be very flexible and dynamic, so I realized it wasn't a great solution I had begun on.
At the lowest level "Zone" in this case, there will need to be a "machines" field, which should return a list of machines (they are in a "machines" table in the db, and not part of the hierarchy, but simply related with an "entity_id" on the "machines" table.
I had schema types and resolvers for all in the above hierarchy: Organization, Branch, Factory, Zone etc, but I was for the most part just repeating myself, so I thought I could turn to interfaces to try to generalize this more.
So instead of doing
{
companies{
name
branchCount
buildingCount
zoneCount
branches {
name
buildingCount
zoneCount
buildings {
name
zoneCount
zones {
name
machines {
name
}
}
}
}
}
}
And having to add schema/resolvers for all the different namings of the entities, I thought this would work:
{
entities(type: "companies") {
name
... on HasEntityCount {
branchCount: entityCount(type: "branch")
buildingCount: entityCount(type: "building")
zoneCount: entityCount(type: "zone")
}
... on HasSubEntities {
entities(type: "branch") {
name
... on HasEntityCount {
buildingCount: entityCount(type: "building")
zoneCount: entityCount(type: "zone")
}
... on HasMachineCount {
machineCount
}
... on HasSubEntities {
entities(type: "building") {
name
... on HasEntityCount {
zoneCount: entityCount(type: "zone")
}
... on HasMachineCount {
machineCount
}
... on HasSubEntities {
entities(type: "zone") {
name
... on HasMachines {
machines
}
}
}
}
}
}
}
}
}
With the interfaces being:
interface HasMachineCount {
machineCount: Int
}
interface HasEntityCount {
entitiyCount(type: String): Int
}
interface HasSubEntities {
entities(
type: String
): [Entity!]
}
interface HasMachines {
machines: [Machine!]
}
interface Entity {
id: ID!
name: String!
type: String!
}
The below works, but I really want to avoid a single type with lots of optional / null fields:
type Entity {
id: ID!
name: String!
type: String!
# Below is what I want to avoid, by using interfaces
# Imagine how this would grow
entityCount
machineCount
entities
machines
}
In my own logic I don't care what the entities are called, only what fields expected. I'd like to avoid a single Entity type with alot of nullable fields on it, so I thought interfaces or unions would be helpful for keeping things separated so I ended up with HasSubEntities, HasEntityCount, HasMachineCount and HasMachines since the bottom entity will not have entities below, and only the bottom entity will have machines. But in the real code there would be much more than the 2, and it could end up with a lot of optional fields, if not utilizing interfaces or unions in some way I think.
There's two separate problems here.
One, GraphQL resolves fields in a top down fashion. Parent fields are always resolved before any children fields. So it's never possible to access the value that a field resolved to from the parent field's resolver (or a "sibling" field's resolver). In the case of fields with an abstract type, this applies to type resolvers as well. A field type will be resolved before any children resolvers are called. The only way to get around this issue is to move the relevant logic from the child resolver to inside the parent resolver.
Two, assuming the somethings field has the type Something (or [Something], etc.), the query you're trying to run will never work because HasBarCount and HasBazCount are not subtypes of Something. When you tell GraphQL that a field has an abstract type (an interface or a union), you're saying that what's returned by the field could be one of several object types that will be narrowed down to exactly one object type at runtime. The possible types are either the types that make up the union, or types that implement the interface.
A union may only be made up of object types, not interfaces or other unions. Similarly, only an object type may implement an interface -- other interfaces or unions may not implement interfaces. Therefore, when using inline fragments with a field that returns an abstract type, the on condition for those inline fragments will always be an object type and must be one of the possible types for the abstract type in question.
Because this is pseudocode, it's not really clear what business rules or use case you're trying to model with this sort of schema. But I can say that there's generally no need to create an interface and have a type implement it unless you're planning on adding a field in your schema that will have that interface as its type.
Edit: At a high level, it sounds like you probably just want to do something like this:
type Query {
entities(type: String!): [Entity!]!
}
interface Entity {
type: String!
# other shared entity fields
}
type EntityWithChildren implements Entity {
type: String!
children: [Entity!]!
}
type EntityWithModels implements Entity {
type: String!
models: [Model!]!
}
The type resolver needs to check for whether we have models, so you'll want to make sure you fetch the related models when you fetch the entity (as opposed to fetching them inside the models resolver). Alternatively, you may be able to add some kind of column to your db that identifies an entity as the "lowest" in the hierarchy, in which case you can just use this property instead.
function resolveType (obj) {
return obj.models ? 'EntityWithModels' : 'EntityWithChildren'
}
Now your query looks like this:
entities {
type
... on EntityWithModels {
models { ... }
}
... on EntityWithChildren {
children {
... on EntityWithModels {
models { ... }
}
... on EntityWithChildren {
# etc.
}
}
}
}
The counts are a bit trickier because of the variability in the entity names and the variability in the depth of the hierarchy. I would suggest just letting the client figure out the counts once it gets the whole graph from the server. If you really want to add count fields, you'd have to have fields like childrenCount, grandchildrenCount, etc. Then the only way to populate those fields correctly would be to fetch the whole graph at the root.
I am looking at GraphQL but am confused why when using a fragment as below, you have to define the "on Character"? could this be anything any name as doesn't explain or have the context on the GraphQL documentation.
query {
leftComparison: hero(id: "1") {
...comparisonFields
}
rightComparison: hero(id: "2") {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
While the example on graphql.org doesn't make this totally obvious, a fragment is always attached to some specific type (can be an object type, interface, or union). Inside the fragment, you can only use fields that exist on the type that's named; the server will check this for you (and clients are capable of checking ahead of time if they want to).
If a field returns an interface or union type, you can similarly only select fields that you know to exist (because an interface provides them), but you can attempt to match on specific types that implement the interface or are members of the union to get more data. This is frequently done with inline fragments, but since a named fragment is attached to a type, you can use named fragments as well. If the schema contains the very generic query
interface Node { id: ID! }
type Query {
node(id: ID!): Node
}
and Character implements Node, then you can plug in the named fragment you have here
query GetCharacterDetails($id: ID!) {
node(id: $id) {
...comparisonFields
}
}
My CMS returns me a list of nodes each having its own nodetype. For each nodetype I have a corresponding GraphQL type defined.
type getContent {
content: [ContentNode]
}
I want a query like:
{
content{
contentType
properties {
${ContentType.getFragment('content', type: $contentType???)}
}
}
}
ContentType would return a correct fragment definition based on type variable provided to it. But how do I get $contentType from parent results?
You can't have fragments that depend on actual value of the parent, because fragments are composed before the query request is actually made to server. There are two different ways to handle this, one is to have fragment vary based on variables and other is to use interface and typed fragments inside your component.
Here is a good answer showing example of using variables: Conditional fragments or embedded root-containers when using Relay with React-Native
For the interface solution, if you have ContentNode an interfaces with implementations like 'ContentNode1' and 'ContentNode2', then you can do something like that:
{
content {
${ContentType.getFragment('content')}
}
}
And in your ContentType component
fragment on ContentNode {
contentType
someOtherCommonField
... on ContentNode1 {
someContent1Field
}
... on ContentNode2 {
someContent2Field
}
}
I was wondering what the significance of the string that follows the query type, in this case "ProvisionQueues", it seems removing this from the string doesn't affect anything - is it just for logging or something. meta data?
mutation ProvisionQueues {
createQueue(name: "new-queue") {
url
}
}
That string is the operation name. If you don't specify a name, the operation is known as an anonymous operation. When practical I like to always specify an operation name though, because it makes it easier for doing things like reading stack traces.
it seems removing this from the string doesn't affect anything
You can only use an anonymous operation when you are performing a single operation. For instance, the following results in an error:
query {
user(id: 1) {
name
}
}
query {
user(id: 2) {
name
}
}
The error:
"message": "This anonymous operation must be the only defined operation."
If you want to learn more, you can check out the GraphQL spec:
If a document contains only one operation, that operation may be unnamed or represented in the shorthand form, which omits both the query keyword and operation name. Otherwise, if a GraphQL query document contains multiple operations, each operation must be named.
Adding to #Eric's answer with another example.
query allNotifications {
notifications {
success
errors
notifications {
id
title
description
attachment
createdAt
}
}
}
query {
users {
errors
success
users {
id
fullName
}
}
}
Notice above that the users query has no operation name. This can be resolved as below.
query allUsers {
users {
errors
success
users {
id
fullName
mohalla
}
}
}