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 field weather that will be populated with information from a (materializing) query called weatherReport with the signature weatherReport(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 and longtitude will be automatically be taken from Location. 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 the weather property can be set to [WeatherReport]. If the type of the property is a single type 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 an id as its argument (for example, author(id: ID!): Author), but the author ID within the Book field is authorID. You can use the arguments 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 is bookCover(bookID: ID!, size: Int):Image and argument inside of an arguments maps the field argument coverSize to the query argument size.

The argument property is not mapped by default, so if it were cover(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.