visit
I've run where someone asked how to integrate their own GraphQL API with a third-party (Shopify) GraphQL API.
In a nutshell, they are building their own GraphQL API and want to integrate it with Shopify's GraphQL API. What sounds like a simple task, is actually quite complex.
They seem to have some experience in the field, so they identified the biggest problem right away: When combining multiple GraphQL APIs, how do you protect certain fields for different groups of users (role-based-access) and what's the right way of injecting different credentials depending on the user and origin?
There are several methods of combining multiple GraphQL APIs, stitching, wrapping, federation and composition are the most common ones. We'll have a look at the three of them, how they can help solve the problem (or not) and what the differences are.
GraphQL Schema stitching is a technique that allows you to combine multiple GraphQL APIs into one. It allows you to combine a set of heterogeneous APIs that don't know about each other. The result is a single combined GraphQL Schema that can be queried by a single GraphQL endpoint.
What's great about this is that as a developer, you can now interact with both APIs as if they were one. Additionally, schema stitching allows you to build "link" between the two schemas. This means that you can query Products from the Shopify API, and "join" them with reviews from your own API.
While schema stitching is a great way to combine multiple GraphQL APIs, this approach also has some downsides.
First, when stitching multiple GraphQL APIs together, you might run into naming conflicts. The more APIs you combine, the more likely it is that you'll run into naming conflicts. Naming conflicts can be resolved by renaming fields or types, but this can be a tedious task. So, it's not a show-stopper, but it's something to keep in mind.
Second, schema stitching is a very manual process. If you want to add links between your schemas, you have to do this manually.
Third, authentication and authorization get more complex when combining multiple GraphQL APIs. Implementing authentication and authorization for a single API is already a complex task, but it's doable. As you own the API implementation, you can implement authentication in a way to "inject" the user object into the resolver context. Inside the resolvers, you're able to access the user object from the context and can use it to implement authorization.
But how does this work if you don't actually own the API implementation? Shopify doesn't understand your user object, and the resolver is actually a "remote-resolver".
Now imagine that, depending on the role of the current user, you want to allow them to use certain fields on the Shopify API, while regular users should not be able to access those fields. Additionally, you need to inject different credentials depending on the user and origin.
This is where schema stitching gets really complicated. It's doable, by adding custom middlewares to intercept the requests and rewrite them, but it definitely shows the limits of schema stitching. We'll look at federation and schema composition later to see how they can solve this problem, better or worse.
Finally, schema stitching comes with another "downside": Schema design, or the opposite.
When you build a single GraphQL API, this API (hopefully) follows certain design principles. The way you name your fields, types and arguments should be consistent. When you look at the Schema, you can clearly see that it's one single API.
Once you start stitching multiple GraphQL APIs together, you end up with an API that follows multiple design principles. There are many ways how GraphQL Schemas can differ from each other, like camel case vs snake case, plural vs singular for field names, input objects vs long lists of arguments, unions for responses to indicate known errors vs error codes in the error object and flat response objects.
If you're using GraphQL as an "internal API ORM", this is fine. However, if you're exposing the GraphQL API to other teams or even the public, you might want to consider a better option than exposing your Frankenstein-stitched-API to the world.
In contrast to schema stitching, schema wrapping doesn't really combine multiple GraphQL APIs side-by-side. Instead, you're wrapping all 3rd party APIs under a single umbrella-API.
The main benefit of schema wrapping is that you have full control over the API design and implementation. You can safely wrap the Shopify API without leaking any implementation details to the outside world. As you're the full owner of this API, you can implement authentication and authorization in a way that fits your needs.
Full control and power over the API design and implementation is great, but this comes at a cost.
Schema stitching has some manual parts, but a lot of the work is done automatically. With schema wrapping, you have to do everything manually. This means, you have to write all resolvers, all middlewares and all the glue code yourself. Additionally, you have to maintain the API implementation yourself. If you're using a 3rd party API, you have to make sure that you're always up-to-date with the latest changes.
In terms of performance, schema wrapping will always run through two layers of resolvers. This can have a negative impact on performance. A single GraphQL API might be able to solve the N+1 problem using the dataloader-pattern. But when you're wrapping multiple GraphQL APIs, you might end up with inefficient execution plans where the dataloader-pattern doesn't work anymore.
An example is when you fetch the current shopping cart from your own API, and want to fetch the current price of the products from the Shopify API. You'll end up making multiple requests for the nested price field. This nested field is not part of the "local" API, so the dataloader-pattern won't work here.
Another option to combine multiple GraphQL APIs is to use Apollo Federation. Apollo Federation is a specification that allows you to combine multiple GraphQL APIs into one. Compared to schema stitching, federation is a more declarative approach.
The goal of federation is to design a single GraphQL API that can be implemented by multiple services. Each service can be implemented by a different team, and each service can be implemented in a different programming language. But all services together form a single GraphQL API.
The main benefit over schema stitching is that you're more likely to end up with a consistent API design. Federation allows you to scale a landscape of GraphQL Microservices across multiple teams.
That said, federation doesn't really solve the problem of merging 3rd party APIs. While it's a great tool when all "Subgraphs" are under your control, it's not really a solution when you want to combine multiple 3rd party APIs.
Shopify will most likely not respect your API design guidelines. Imagine you'd have to achieve consistent API design across all companies who expose their APIs publicly. It's not really feasible.
So, while federation is a great tool, it doesn't really help us with this problem.
So far, we've discussed the tradeoffs between the two possible approaches, schema stitching and schema wrapping, while we've dismissed federation as it solves a different problem.
Schema wrapping, being the most expensive one to implement, is really only an option if you have to expose the API to the public. At the same time, schema stitching still clocks in relatively high in terms of complexity and maintenance.
That's where schema composition comes in handy. Schema composition embraces the idea of . Naming conflicts are resolved using namespacing, allowing us to combine multiple APIs fully automatically.
The way we've implemented schema composition at WunderGraph is that we're not actually exposing the composed GraphQL API. Instead, we're using GraphQL Operations as a tool to define our actual API, . GraphQL is simply the language to manage the (API) dependencies, but the resulting API is not a GraphQL API.
That's why we call the resulting Schema the . It's virtual, because it only really exists during development.
Here's an example of how a GraphQL Operation in a composed virtual graph looks like:
query (
$continent: String!
# the @internal directive removes the $capital variable from the public API
# this means, the user can't set it manually
# this variable is our JOIN key
$capital: String! @internal
) {
countries_countries(filter: { continent: { eq: $continent } }) {
code
name
# using the @export directive, we can export the value of the field `capital` into the JOIN key ($capital)
capital @export(as: "capital")
# the _join field returns the type Query!
# it exists on every object type so you can everywhere in your Query documents
_join {
# once we're inside the _join field, we can use the $capital variable to join the weather API
weather_getCityByName(name: $capital) {
weather {
temperature {
max
}
summary {
title
description
}
}
}
}
}
}
The benefits of this approach are that you can combine multiple APIs in just a few clicks. The caller of the API doesn't even have to know that the API is composed of multiple APIs.
This means, we're able to expose the JSON-HTTP API as one single API. Authentication and Authorization across all APIs can be handled in a single place.
If some APIs should only be accessible to special users, like admins, you can apply role-based-access at the API Gateway level. Additionally, you can handle injecting credentials, like admin or super admin tokens from within the API composition layer. API Composition is designed for exactly this use case.
For each origin, different rules might apply on how to authenticate against the API. If we're treating APIs as dependencies, we can implicitly handle authentication and authorization for each API.
The main downside of this approach is that we're not exposing a GraphQL API. Instead, we're exposing a JSON-HTTP API.
However, I've found that this is actually more of a benefit than a downside. When deploying a GraphQL API to production, you're very unlikely to change the GraphQL Operations at runtime. So, if we're not changing the Operations, we don't really need to expose a dynamic API. We can simply expose a fixed set of GraphQL Operations in the form of a JSON-HTTP API.
More and more tools emerge to "secure" GraphQL APIs. When you're hiding the GraphQL API behind a JSON-HTTP API, the security concerns are mostly gone.
The real downside of this approach is that it doesn't work if your intention is to expose the API to the public. While most GraphQL APIs are used internally, if your use case is to expose the API to the public, you're better off using schema wrapping.
As described above, the complexity of adding more APIs to a composed API is constant: O(1). Compared to schema stitching, where the complexity is exponential: O(n^2), assuming that n is the number of APIs.
This means, you can add as many APIs as you want to your composed API without any additional complexity. The more APIs you combine with schema stitching, the more edge cases, like naming collisions, you'll have to deal with.
Here's an example of how to compose two APIs:
// wundergraph.config.ts
const weather = introspect.graphql({
apiNamespace: 'weather',
url: '//graphql-weather-api.herokuapp.com/',
})
const countries = introspect.graphql({
apiNamespace: 'countries',
url: '//countries.trevorblades.com/',
})
Simply make sure that you're using different namespaces for each API. That's the power of thinking in "APIs as dependencies".
One additional benefit of Schema Composition is that you can use it together with Schema Stitching and Federation. You're not forced to using one pattern exclusively. Why not build some GraphQL Microservices with Federation and use Schema Composition to join some 3rd party APIs on top?
It's a great way to combine the best of both worlds, scaling your internal teams while still being able to leverage 3rd party APIs without manually stitching them together.
Now that we've discussed the different approaches, let's summarize when to use which approach.
Schema wrapping is the most expensive solution to implement, but it's also the most flexible one.
Wrapping 3rd party APIs is the right way to go when you want to expose the API to the public and don't want to expose the underlying APIs.
Schema Stitching allows you to combine multiple heterogeneous APIs in a semi-automatic way. It doesn't scale well in terms of number of APIs, but if it's a small number, it might work. Keep in mind that the resulting API doesn't follow a single set of design principles.
Apollo Federation is a great way to build GraphQL Microservices. It works best when you have full control over all the APIs, which is usually the case when all API owners belong to the same company.
In terms of integrating 3rd party APIs, federation is not the right approach.
Schema Composition or thinking in "APIs as dependencies" is the right approach when you need to compose a large number of APIs and want to expose the "API Composition" as a single API.
Schema Composition can be used together with Schema Stitching and Federation, allowing you to combine the best of all worlds.
In this article, we've discussed the different approaches to combine multiple GraphQL APIs. Depending on your requirements, you might want to use one approach over the other.
If you're interested in learning more about GraphQL Schema Composition, here's an , and another one , as we've seen in this article.
WunderGraph Cloud is a hosted Serverless (GraphQL) API Gateway that's currently in the works. It allows you to create API compositions locally and deploy them to the cloud in less than 30 seconds. We're looking for early adopters to help us shape the product. If you're interested, and be amongst the first to try it out for free!