Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use GraphQL to retrieve an object that contains an array of objects with different schemas

I am trying to write a query to retrieve an object with the property linkedCards that contains an array of objects with different schemas.

I have 3 different schemas (built in Contentful):

CardA example:

{
    id: 42,
    productName: 'Laptop',
    price: 999
}

CardB example:

{
    id: 999,
    title: 'Buy our refurbished Laptops today!'
}

CardC example:

{
    id: 100,
    linkedCards: [
        {
            id: 42,
            productName: 'Laptop',
            price: 999
        },
        {
            id: 999,
            title: 'Buy our refurbished Laptops today!'
        }
    ]
}

Query:

allCardC() {
    nodes {
        linkedCards {
            id
            title
        }
    }
}

When I try to run the following GraphQL query I get "Cannot query field "title" on type "CardACardBUnion". Did you mean to use an inline fragment on "CardA" or "CardB"?"

Is there a built-in way to do this or can I use the ids of CardA & CardB somehow? Perhaps have one query to get the ids of the cards in linkedCards and another query to get said cards?

like image 214
Christoph Anderson Avatar asked Apr 15 '19 22:04

Christoph Anderson


People also ask

How do you return an array of objects in GraphQL?

const express = require("express"); const graphqlHTTP = require("express-graphql"); const schema = require("./schema/schema"); const app = express(); app. use("/graphql", graphqlHTTP({ schema, graphiql: true })); app. listen(4000, () => { console.

Can GraphQL query return array?

GraphQL provides a query language to define the shape of data you'd like returned from an HTTP API on a server and a library to help make it happen. It's easy to return a single item or multiple items.


2 Answers

As the error indicates, you need to use an inline fragment when querying a field that resolves to a union:

allCardC {
    nodes {
        linkedCards {
            ... on CardA {
              id
              productName
              price
            }
            ... on CardB {
              id
              title
            }
        }
    }
}

Fragments can be defined inline within a selection set. This is done to conditionally include fields based on their runtime type.

Unlike interfaces or regular object types, unions do not specify any particular fields, only the types that make up the union. That means a selection set for a field that returns a union must always use fragments to conditionally specify the fields depending on the actual type that the field resolves to.

It's like saying, "if this is the actual type of the returned object, request these fields".

like image 98
Daniel Rearden Avatar answered Oct 19 '22 21:10

Daniel Rearden


You may find it useful to use a GraphQL interface to specify the fields that every card type has in common.

interface Card {
  id: ID!
}
# type CardA implements Card { ... }
type CardB implements Card {
  id: ID!
  title: String!
}
type CardC implements Card {
  id: ID!
  linkedCards: [Card!]!
}

As @DanielRearden's answer suggests you still need to use (inline) fragments to select fields that are specific to one of the card types, but now that you know every card has an id field, you can select that directly.

allCardC {
    nodes {
        linkedCards {
            id
            ... on CardB { title }
        }
    }
}
like image 24
David Maze Avatar answered Oct 19 '22 21:10

David Maze