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

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?

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".

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 }
}
}
}

Related

Custom type replacement with Hot Chocolate 12

My schema defines an Employee type that has 15 or so fields on it. It's then used multiple times in other areas:
type Employee {
active: Boolean!
name: String
id: Int!
...
}
type Room {
owner: Employee!
delegate: Employee
}
99% of the time, I want the same three fields when returning data on an employee. So I end up writing a query like this:
query {
rooms {
owner: {
name
id
active
}
}
}
which is repetative when something has multiple employees. Is there a way to define some type of transformation so that instead I'd do something like:
query {
rooms {
owner: #commEmpFields
}
}
My initial thought was to create a custom ObjectType<Employee> but then I realized that I didn't know how to map that to the query.

I dont want to redefine properties when defining a GraphQL type. Is there a way to get past it? [duplicate]

Is it possible to use inheritance with GraphQL input types?
Something like that (this, of course, doesn't work with input types):
interface UserInputInterface {
firstName: String
lastName: String
}
input UserInput implements UserInputInterface {
password: String!
}
input UserChangesInput implements UserInputInterface {
id: ID!
password: String
}
No, the spec does not allow input types to implement interfaces. And GraphQL type system in general does not define any form of inheritance (the extends keyword adds fields to an existing type, and isn't for inheritance). The spec is intentionally constrained to stay simple. This means that you're stuck repeating fields across input types.
That said, depending on the way you construct your schema, you could build some kind of type transformer that appends the common fields programmatically based on some meta-data, e.g. a directive.
Better yet, you might be able to solve your problem via composition (always keep composition over inheritance in mind).
E.g.
input Name {
firstName: String
lastName: String
}
input UserInput {
name: Name
password: String!
}
input UserChangesInput {
name: Name
id: ID!
password: String
}
The client now has to send an object a level deeper, but that doesn't sound like much of a price for avoiding big repeating chunks. It might actually be good for the client as well, as they can now have common logic for building names, regardless of the query/mutation using them.
In this example, where it's only 2 simple fields, this approach is an overkill, but in general - I'd say it's the way to go.
Starting with the June2018 stable version of the GraphQL spec, an Input Object type can extend another Input Object type:
Input object type extensions are used to represent an input object type which has been extended from some original input object type.
This isn't inheritance per se; you can only extend the base type, not create new types based on it:
extend input MyInput {
NewField: String
}
Note there is no name for the new type; the existing MyInput type is extended.
The JavaScript reference implementation has implemented Input Object extensions in GraphQL.js v14 (June 2018), though it's unclear how to actually pass the extended input fields to a query without getting an error.
For actual type inheritance, see the graphql-s2s library.
It's doable using a custom directive.
Code Summary
const typeDefs = gql`
directive #inherits(type: String!) on OBJECT
type Car {
manufacturer: String
color: String
}
type Tesla #inherits(type: "Car") {
manufacturer: String
papa: String
model: String
}
type Query {
tesla: Tesla
}
`;
const resolvers = {
Query: {
tesla: () => ({ model: 'S' }),
},
Car: {
manufacturer: () => 'Ford',
color: () => 'Orange',
},
Tesla: {
manufacturer: () => 'Tesla, Inc',
papa: () => 'Elon',
},
};
class InheritsDirective extends SchemaDirectiveVisitor {
visitObject(type) {
const fields = type.getFields();
const baseType = this.schema.getTypeMap()[this.args.type];
Object.entries(baseType.getFields()).forEach(([name, field]) => {
if (fields[name] === undefined) {
fields[name] = { ...field };
}
});
}
}
const schemaDirectives = {
inherits: InheritsDirective,
};
Query:
query {
tesla {
manufacturer
papa
color
model
}
}
Output:
{
"data": {
"tesla": {
"manufacturer": "Tesla, Inc",
"papa": "Elon",
"color": "Orange",
"model": "S",
}
}
}
Working example at https://github.com/jeanbmar/graphql-inherits.
If you came here looking for an explanation for the "implements", keyword, here it is:
An object type must be a super‐set of all interfaces it implements. The object type must include a field of the same name for every field defined in an interface.
(Excerpt taken from the June 2018 GraphQL spec.)
Here's an example
interface Foo {
id: ID!
foo: Int!
}
type Bar implements Foo #entity {
id: ID!;
foo: Int!;
bar: Int!;
}
So the Bar type doesn't inherit from the Foo interface, but it implements it. The former must include all the fields that are listed in the latter.
I think that this is a nice way to annotate types that should be like other types.

Different field types depending on args

My database is structured as following:
There is a Product table:
id (integer)
manufacture_id (integer)
state (boolean)
And there is a Product_translations table
product_id (integer)
language_id (integer)
name (string)
description (string)
When querying for a product I would like to be able to receive a name and description directly if I provide a language id as an argument, or receive a list of translations with all the language id's and name/description instead if I don't provide a language id.
Is there a way to achieve this without creating two different Types and two different Queries?
Yes and no.
When you specify the return type for your query (let's call it getProduct), you can only specify one type (or a union or interface... more on that later). That type (Product) will have an immutable list of fields. When you make a request to your server, you will have to identify a subset of those fields to have the server return. With this in mind, it's not possible (at least natively) to send a query have the server return a different subset of fields depending on those arguments.
That said, what you can do is define a type that includes all the possible fields, like this:
type Product {
id: ID!
name: String
description: String
translations: [Translation!]!
}
Then within your resolver for getProduct, you can fetch the product from the table and then check whether language was provided as an argument. If it wasn't, fetch the list of translations and set your product's translations property to it. If language was provided, fetch just that translation, use it to populate the name and description properties of the product, and set translations to an empty array.
In this way, depending on whether language is passed in as an argument, your returned Product will contain either A) null for name and description and a populated list of translations; or B) a name and description and an empty array for translations.
There is, IMHO, also a more elegant alternative: unions and interfaces.
As before, you'd need to construct your returned object appropriately based on whether the language argument is present. But instead of a type, you return a Union or Interface and then utilize the __resolveType field to return a specific type (each with different fields).
There's two advantages to this approach: One, you avoid returning unnecessary null fields. And two, if you use Apollo as a client, it automatically tacks on a __typename field that you can use on the client-side to easily determine the type that was actually returned by a query.
Here's an example you can plug right into Launchpad to play around with:
import { makeExecutableSchema } from 'graphql-tools';
const typeDefs = `
type Query {
getProduct (id: ID, language: ID): ProductInterface
},
type Product implements ProductInterface {
id: ID
translations: [Translation!]!
},
type TranslatedProduct implements ProductInterface {
id: ID
name: String
description: String
},
type Translation {
language: ID
name: String
description: String
},
interface ProductInterface {
id: ID
}
`;
const products = [
{
id: '1',
translations: [
{
language: '100',
name: 'Foo',
description: 'Foo!'
},
{
language: '200',
name: 'Qux',
description: 'Qux!'
}
]
}
]
const resolvers = {
Query: {
getProduct: (root, {id, language}, context) => {
const product = products.find(p => p.id === id)
if (language) {
product.translation = product.translations.find(t => t.language === language)
}
return product
},
},
ProductInterface: {
__resolveType: (root) => {
if (root.translation) return 'TranslatedProduct'
return 'Product'
}
},
TranslatedProduct: {
name: (root) => root.translation.name,
description: (root) => root.translation.description
}
};
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
You can then request a query like this:
{
getProduct (id: "1", language: "200") {
__typename
... on Product {
translations {
language
name
description
}
}
... on TranslatedProduct {
name
description
}
}
}

Share structure between GraphQL schemas

I have a Apollo GraphQL server talking to an API returning responses with roughly the following structure:
{
"pagination": {
"page": 1,
// more stuff
},
sorting: {
// even more stuff
},
data: [ // Actual data ]
}
This structure is going to be shared across pretty much all responses from this API, that I'm using extensively. data is going to be an array most of the time, but can also be an object.
How can I write this in an efficient way, so that I don't have to repeat all these pagination and sorting fields on every data type in my schemas?
Thanks a lot!
I've sorted your problem by creating a lib called graphql-s2s. It enhances your schema by adding support for type inheritance, generic types and metadata. In your case, creating a generic type for your Paginated object could be a viable solution. Here is an example:
const { transpileSchema } = require('graphql-s2s')
const { makeExecutableSchema } = require('graphql-tools')
const schema = `
type Paged<T> {
data: [T]
cursor: ID
}
type Node {
id: ID!
creationDate: String
}
type Person inherits Node {
firstname: String!
middlename: String
lastname: String!
age: Int!
gender: String
}
type Teacher inherits Person {
title: String!
}
type Student inherits Person {
nickname: String!
questions: Paged<Question>
}
type Question inherits Node {
name: String!
text: String!
}
type Query {
students: Paged<Student>
teachers: Paged<Teacher>
}
`
const executableSchema = makeExecutableSchema({
typeDefs: [transpileSchema(schema)],
resolvers: resolver
})
I've written more details about this here (in Part II).
When you define your schema, you will end up abstracting out pagination, sorting, etc. as separate types. So the schema will look something like:
type Bar {
pagination: Pagination
sorting: SortingOptions
data: BarData # I'm an object
}
type Foo {
pagination: Pagination
sorting: SortingOptions
data: [FooData] # I'm an array
}
# more types similar to above
type Pagination {
page: Int
# more fields
}
type SortingOptions {
# more fields
}
type BarData {
# more fields
}
So you won't have to list each field within Pagination multiple times regardless. Each type that uses Pagination, however, will still need to specify it as a field -- there's no escaping that requirement.
Alternatively, you could set up a single Type to use for all your objects. In this case, the data field would be an Interface (Data), with FooData, BarData, etc. each implementing it. In your resolver for Data, you would define a __resolveType function to determine which kind of Data to return. You can pass in a typename variable with your query and then use that variable in the __resolveType function to return the correct type.
You can see a good example of Interface in action in the Apollo docs.
The downside to this latter approach is that you have to return either a single Data object or an Array of them -- you can't mix and match -- so you would probably have to change the structure of the returned object to make it work.

Can a GraphQL input type inherit from another type or interface?

Is it possible to use inheritance with GraphQL input types?
Something like that (this, of course, doesn't work with input types):
interface UserInputInterface {
firstName: String
lastName: String
}
input UserInput implements UserInputInterface {
password: String!
}
input UserChangesInput implements UserInputInterface {
id: ID!
password: String
}
No, the spec does not allow input types to implement interfaces. And GraphQL type system in general does not define any form of inheritance (the extends keyword adds fields to an existing type, and isn't for inheritance). The spec is intentionally constrained to stay simple. This means that you're stuck repeating fields across input types.
That said, depending on the way you construct your schema, you could build some kind of type transformer that appends the common fields programmatically based on some meta-data, e.g. a directive. Here's one such implementation.
Better yet, you might be able to solve your problem via composition (always keep composition over inheritance in mind).
E.g.
input Name {
firstName: String
lastName: String
}
input UserInput {
name: Name
password: String!
}
input UserChangesInput {
name: Name
id: ID!
password: String
}
The client now has to send an object a level deeper, but that doesn't sound like much of a price for avoiding big repeating chunks. It might actually be good for the client as well, as they can now have common logic for building names, regardless of the query/mutation using them.
In this example, where it's only 2 simple fields, this approach is an overkill, but in general - I'd say it's the way to go.
Starting with the June2018 stable version of the GraphQL spec, an Input Object type can extend another Input Object type:
Input object type extensions are used to represent an input object type which has been extended from some original input object type.
This isn't inheritance per se; you can only extend the base type, not create new types based on it:
extend input MyInput {
NewField: String
}
Note there is no name for the new type; the existing MyInput type is extended.
The JavaScript reference implementation has implemented Input Object extensions in GraphQL.js v14 (June 2018), though it's unclear how to actually pass the extended input fields to a query without getting an error.
For actual type inheritance, see the graphql-s2s library.
It's doable using a custom directive.
Code Summary
const typeDefs = gql`
directive #inherits(type: String!) on OBJECT
type Car {
manufacturer: String
color: String
}
type Tesla #inherits(type: "Car") {
manufacturer: String
papa: String
model: String
}
type Query {
tesla: Tesla
}
`;
const resolvers = {
Query: {
tesla: () => ({ model: 'S' }),
},
Car: {
manufacturer: () => 'Ford',
color: () => 'Orange',
},
Tesla: {
manufacturer: () => 'Tesla, Inc',
papa: () => 'Elon',
},
};
class InheritsDirective extends SchemaDirectiveVisitor {
visitObject(type) {
const fields = type.getFields();
const baseType = this.schema.getTypeMap()[this.args.type];
Object.entries(baseType.getFields()).forEach(([name, field]) => {
if (fields[name] === undefined) {
fields[name] = { ...field };
}
});
}
}
const schemaDirectives = {
inherits: InheritsDirective,
};
Query:
query {
tesla {
manufacturer
papa
color
model
}
}
Output:
{
"data": {
"tesla": {
"manufacturer": "Tesla, Inc",
"papa": "Elon",
"color": "Orange",
"model": "S",
}
}
}
Working example at https://github.com/jeanbmar/graphql-inherits.
If you came here looking for an explanation for the "implements", keyword, here it is:
An object type must be a super‐set of all interfaces it implements. The object type must include a field of the same name for every field defined in an interface.
(Excerpt taken from the June 2018 GraphQL spec.)
Here's an example
interface Foo {
id: ID!
foo: Int!
}
type Bar implements Foo #entity {
id: ID!;
foo: Int!;
bar: Int!;
}
So the Bar type doesn't inherit from the Foo interface, but it implements it. The former must include all the fields that are listed in the latter.
I think that this is a nice way to annotate types that should be like other types.

Resources