Linking multiple types together
Connect multiple GraphQL types using the API Connect Essentials custom
@materializer
directive.
The API Connect Essentials custom @materializer
directive for combining data returned from two types into
one. It's useful for when you're making a call to two separate endpoints or backends, or assembling
multiple GraphQL types from a nested JSON response.
Configuration properties
query
- This property is required.
The
query
property specifies the name of the query within the GraphQL schema that will be used to populate the data within your type.The following example shows the GraphQL for a type
Location
with a fieldweather
that will be populated with information from a (materializing) query calledweatherReport
with the signatureweatherReport(latitude: Float!, longitude: Float!): WeatherReport
:type Location { city: String country: String! ip: String! latitude: Float! longitude: Float! weather: WeatherReport @materializer(query: "weatherReport") }
As expected,
@materializer
will naturally match fields to the arguments of the materializing query. So in this case,latitude
andlongtitude
will be automatically be taken fromLocation
. This default behavior makes using materializing queries straightforward for the most common usages.The type for a field (for example,
weather
) must match the return type of the query (for example,weatherReport
).If the materializing query (
weatherReport
in this case) returns an array, the type of theweather
property can be set to[WeatherReport]
. If the type of the property is a singletype
instance but the result of the query is an array, then only the first result will be returned.What about the cases where the natural match doesn't work? For that, we have an
arguments
qualifier for@materializer
. arguments
- This property is optional and is used where the field names of the type do not exactly match the
arguments.
For example, the following
author
query expects anid
as its argument (for example,author(id: ID!): Author
), but the author ID within theBook
field isauthorID
. You can use thearguments
property to map the queries argument name (id
) to the field name on the type (authorID
):type Book { id: ID! name: String! originalPublishingDate: Date! authorID: ID! author: Author @materializer( query: "author" arguments: [{ name: "id" field: "authorID"}] ) }
GraphQL also allows you to write
arguments: { name: "id" field: "authorID"}
There is a less common usage where you have:
type Book { id: ID! name: String! originalPublishingDate: Date! authorID: ID! cover(coverSize: Int): Image @materializer( query: "bookCover" arguments: [ { name: "size" argument: "coverSize"}, { name: "bookID" field: "id"} ] ) }
The
bookCover
signature isbookCover(bookID: ID!, size: Int):Image
andargument
inside of anarguments
maps the field argumentcoverSize
to the query argumentsize
.The
argument
property is not mapped by default, so if it werecover(coverSize: Int): Image
, then you must add{ name: "size" argument: "size"}
.If a query argument cannot be mapped using the default method or arguments, it will be null. For non-nullable values, this will result in an error.
Handling nested JSON results
The results from REST APIs are often returned as nested objects. You can use
setters
to map these to a flat result, but in many cases you might want to map the
nested objects to separate GraphQL types. This can be done with the @materializer
directive (for more information, see About
the @rest
directive).
For example, a partial JSON result from the random user API might look like the following:
{
"gender": "male",
"name": {
"first": "Brecht",
"last": "Polfliet"
},
"location": {
"city": "Ter Apelkanaal",
"state": "Utrecht",
"country": "Netherlands",
},
"email": "brecht.polfliet@example.com"
}
You can use setters
to map the name fields to the main
randomUser
type, but you want to assign the results under location
to a Location
object. Use @materializer
to fulfill the data,
returning a Location
type as follows:
type RandomUser {
gender: String!
firstName: String!
lastName: String!
email: String!
location: Location @materializer(query: "userLocation")
}
type Location {
city: String!
state: String!
country: String!
}
type Query {
randomUser: RandomUser
@rest(
endpoint: "https://randomuser.me/api"
resultroot: "results[]"
setters: [
{ field: "firstName", path: "name.first" }
{ field: "lastName", path: "name.last" }
]
)
userLocation: Location
@rest(
endpoint: "https://randomuser.me/api"
resultroot: "results[].location"
)
}
The root (for example, resultroot
) for location is the nested structure for
results[].location
.
But would this result in two different API calls? No. If the first and second URL match exactly, the second query will hit the cache and the second query will not be made. There are other factors that can affect this, but the goal is to offer the benefits of independence with efficiency where possible.