typeMerging transform

Type Merging allows you to combine multiple sources by merging a type from each source. For example, you could combine responses from two different APIs on a single field, provided you rename the fields you want to stitch to the same name.

For example, you could combine responses from two different APIs on a single field, provided you rename the fields you want to stitch to the same name.

What is Type Merging?

Imagine you have a mesh with two different GraphQL sources, Books and Authors, defined as the following:

# Authors
type Query {
  authors(ids: [ID!]): [Author!]!
  author(id: ID!): Author!
}

type Author {
  id: ID!
  name: String!
}
# Books
type Query {
  books(ids: [ID!]): [Book!]!
  book(id: ID!): Book!
  authorWithBooks(id: ID!): AuthorWithBooks!
  authorsWithBooks(ids: [ID!]): [AuthorWithBooks!]!
}

type Book {
  id: ID!
  title: String!
  authorId: ID!
}

type AuthorWithBooks {
  id: ID!
  books: [Book!]!
}

If you wanted to rename AuthorWithBooks to Author using the Rename transform, you would create the following mesh.

[
    {
        "sources": [
            {
                "name": "BookService",
                "handler": null,
                "transforms": [
                    {
                        "rename": {
                            "renames": [
                                {
                                    "from": {
                                        "type": "AuthorWithBooks"
                                    },
                                    "to": {
                                        "type": "Author"
                                    }
                                },
                                {
                                    "from": {
                                        "type": "Query",
                                        "field": "authorWithBooks"
                                    },
                                    "to": {
                                        "type": "Query",
                                        "field": "author"
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        ]
    }
]

After that rename, you would expect the following query to work, but it will fail because the mesh does not know which field belongs to which source and how to combine those.

{
  author(id: 0) {
    id # This field is common
    name # This field is from `AuthorService`
    # This field is from `BookService`
    books {
      id
      title
    }
  }
}

You could add additionalResolvers, extract books from AuthorWithBooks, and return it as a books field of Author type, but this is unnecessarily complicated. So instead, we'll use Type Merging.

The following example indicates how to fetch entities from different sources:

{
    "sources": [
        {
            "name": "AuthorService",
            "handler": null,
            "transforms": [
                {
                    "typeMerging": {
                        "queryFields": [
                            {
                                "queryFieldName": "author",
                                "keyField": "id"
                            }
                        ]
                    }
                }
            ]
        },
        {
            "name": "BookService",
            "handler": null,
            "transforms": [
                {
                    "rename": {
                        "renames": [
                            {
                                "from": {
                                    "type": "AuthorWithBooks"
                                },
                                "to": {
                                    "type": "Author"
                                }
                            },
                            {
                                "from": {
                                    "type": "Query",
                                    "field": "authorWithBooks"
                                },
                                "to": {
                                    "type": "Query",
                                    "field": "author"
                                }
                            },
                            {
                                "from": {
                                    "type": "Query",
                                    "field": "authorsWithBooks"
                                },
                                "to": {
                                    "type": "Query",
                                    "field": "authors"
                                }
                            }
                        ]
                    }
                },
                {
                    "typeMerging": {
                        "queryFields": [
                            {
                                "queryFieldName": "book",
                                "keyField": "id"
                            },
                            {
                                "queryFieldName": "author",
                                "keyField": "id"
                            }
                        ]
                    }
                }
            ]
        }
    ]
}

Now the previous query will work as expected.

Prevent N+1 problem with Type Merging

The previous example works fine, but there is an N+1 problem. It sends n requests for n entities. But we have authors and books. Type Merging is smart enough to handle batching if you point it to a field that returns a list of entities. Let's update our mesh to the following:

{
    "sources": [
        {
            "name": "AuthorService",
            "handler": null,
            "transforms": [
                {
                    "typeMerging": {
                        "queryFields": [
                            {
                                "queryFieldName": "authors",
                                "keyField": "id"
                            }
                        ]
                    }
                }
            ]
        },
        {
            "name": "BookService",
            "handler": null,
            "transforms": [
                {
                    "rename": {
                        "renames": [
                            {
                                "from": {
                                    "type": "AuthorWithBooks"
                                },
                                "to": {
                                    "type": "Author"
                                }
                            },
                            {
                                "from": {
                                    "type": "Query",
                                    "field": "authorWithBooks"
                                },
                                "to": {
                                    "type": "Query",
                                    "field": "author"
                                }
                            },
                            {
                                "from": {
                                    "type": "Query",
                                    "field": "authorsWithBooks"
                                },
                                "to": {
                                    "type": "Query",
                                    "field": "authors"
                                }
                            }
                        ]
                    }
                },
                {
                    "typeMerging": {
                        "queryFields": [
                            {
                                "queryFieldName": "books",
                                "keyField": "id"
                            },
                            {
                                "queryFieldName": "authors",
                                "keyField": "id"
                            }
                        ]
                    }
                }
            ]
        }
    ]
}

And now it batches the requests to the inner sources.

Using the Type Merging Transform

{
  "sources": [
    {
      "name": "AuthorService",
      "handler": {
        "graphql": {
          "endpoint": "https://my-site.com/author-service-schema"
        }
      },
      "transforms": [
        {
          "typeMerging": {
            "queryFields": [
              {
                "queryFieldName": "authors",
                "keyField": "id"
              }
            ]
          }
        }
      ]
    },
    {
      "name": "BookService",
      "handler": {
        "graphql": {
          "endpoint": "https://my-site.com/book-service-schema.js"
        }
      },
      "transforms": [
        {
          "rename": {
            "renames": [
              {
                "from": {
                  "type": "AuthorWithBooks"
                },
                "to": {
                  "type": "Author"
                }
              },
              {
                "from": {
                  "type": "Query",
                  "field": "authorWithBooks"
                },
                "to": {
                  "type": "Query",
                  "field": "author"
                }
              }
            ]
          }
        },
        {
          "typeMerging": {
            "queryFields": [
              {
                "queryFieldName": "book",
                "keyField": "id"
              },
              {
                "queryFieldName": "author",
                "keyField": "id"
              }
            ]
          }
        }
      ]
    }
  ]
}

Config API Reference