Schema stitching is the ability to create a single GraphQL schema from multiple underlying GraphQL APIs.
One of the main benefits of GraphQL is that you can query all of your data as part of one schema, and get everything you need in one request. But as your schema grows, it might become cumbersome to manage it all as one codebase, and it starts to make sense to split it into different modules. You may also want to decompose your schema into separate microservices, which can be developed and deployed independently.
In both cases, you use
mergeSchemas to combine multiple GraphQL schemas together and produce a merged schema that knows how to delegate parts of the query to the relevant subschemas. These subschemas can be either local to the server, or running on a remote server. They can even be services offered by 3rd parties, allowing you to connect to external data and create mashups.
Working with remote schemas
In order to merge with a remote schema, you should first use makeRemoteExecutableSchema to create a local proxy for the schema that knows how to call the remote endpoint. You can then merge with that proxy the same way you would merge with a locally implemented schema.
In this example we’ll stitch together two very simple schemas. It doesn’t matter whether these are local or proxies created with
makeRemoteExecutableSchema, because the merging itself would be the same.
In this case, we’re dealing with two schemas that implement a system with authors and “chirps” - small snippets of text that they can post.
This gives you a new schema with the root fields on
Query from both schemas:
That means you now have a single schema that allows you to ask for
chirpsByAuthorId in one query for example.
Adding resolvers between schemas
Proxying the root fields is a great start, but many cases however you’ll want to add the ability to navigate from one schema to another. In this example, you might want to be able to get from a particular author to their chirps, or from a chirp to its author. This is more than a convenience once you move beyond querying for objects by a specific id. If you want to get the authors for the
latestChirps for example, you have no way of knowing the
authorIds in advance, so you wouldn’t be able to get the authors in the same query.
To add the ability to navigate between types, you need to extend existing types with fields that can take you from one to the other. You can do that the same way you add the other parts of the schema:
We can now merge these three schemas together:
You won’t be able to query
Chirp.author yet however, because the merged schema doesn’t have resolvers defined for these fields. We’ll have to define our own implementation of these.
So what should these resolvers look like?
When we resolve
Chirp.author, we want to delegate to the revelant root fields. To get from a user to its chirps for example, we’ll want to use the
id of the user to call
chirpsByAuthorId. And to get from a chirp to its author, we can use the chirp’s
authorId field to call into
Resolvers specified as part of
mergeSchema have access to a
delegate function that allows you to delegate to root fields.
In order to delegate to these root fields, we’ll need to make sure we’ve actually requested the
id of the user or the
authorId of the chirp. To avoid forcing users to add these to their queries manually, resolvers on a merged schema can define a fragment that specifies the required fields, and these will be added to the query automatically.
A complete implementation of schema stitching for these schemas would look like this:
For a more complicated example involving properties and bookings, with implementations of all of the resolvers, check out the Launchpad links below:
This is the main function that implements schema stitching. Read below for a description of each option.
schemas is an array of either
GraphQLSchema objects or strings. For strings, only
extend type declarations will be used. Passing strings is useful to add fields to existing types to link schemas together, as described in the example above.
resolvers is an optional function that takes one argument -
mergeInfo - and returns resolvers in the same format as makeExecutableSchema. One addition to the resolver format is the possibility to specify a
fragment for a resolver.
fragment must be a GraphQL fragment definition, and allows you to specify which fields from the parent schema are required for the resolver to function correctly.
mergeInfo currenty is an object with one property -
delegate. It looks like this:
delegate takes the operation type (
mutation) and root field names, together with the GraphQL execution context
and resolve info, as well as arguments for the root field. It delegates to
one of the merged schema and makes sure that only relevant fields are requested.
onTypeConflict lets you customize type resolving logic. The default logic is to
take the first encountered type of all the types with the same name. This
method allows customization of this behavior, for example by taking another type or
merging types together.