GraphQL Schema Stitching in action with Apollo
For the past year, I’ve been interested in everything concerning GraphQL.
This article is made with the intention of focusing more on real case scenarios that you will likely face in a project when using GraphQL.
In this article, I will talk about schema stitching. Schema stitching is the technology that allows having a single GraphQL schema made by combining several underlying API’s.
So from the API consumer perspective, it deals with just one GraphQL API, but behind the scenes, this API is just a facade of the underlying API’s.
One of the cool things of schema stitching is that these underlying API’s can be local (“resolved” by your server), or remote (“resolved” by an existing API) . If you want to have an overview of schema stitching, I recommend you to read this article.
Let’s start with a real example!
For that, I’m going to use an existing GraphQL API like the one offered by commercetools. I’m using this API as an example because for the last years I’ve been using it, so I’m familiar with it.
If you don’t know commercetools, it’s an API based e-commerce platform that offers an API to deal with the main entities of an e-commerce site (products, customers, orders, etc…). Besides its REST API, also offers a GraphQL API to deal with some entities.
One of the drawbacks of the commercetools GraphQL API is that it doesn’t support all entities of the platform as opposed to its REST API (it supports products, categories, discounts… but there is no support for carts, orders, and customers).
This, it’s the perfect scenario for schema stitching, because if you want to have a single GraphQL API to manage all commercetool entities, you can create the remaining schema and merge it with the existing commercetools GraphQL schema.
Thus, this would result in having one GraphQL API. Cool! 😎.
GraphQL is not only a query language, it’s also a group of tools and libraries around it. To name a few, it includes: Apollo GraphQL, (which entails Apollo Client, Apollo Server, and Apollo Engine), Relay, GraphQL Yoga, Graphcool and Launchpad.
Hence, after learning some basics of the GraphQL specifics, you will be
able to finally develop a GraphQL API using one of the aforementioned tools and libraries.
In our implementation, we will use Apollo Server, which is a library that connects a Node.js server with a GraphQL schema. There are Apollo Server implementations for the most known Node.js servers such as Express, Hapi, Koa, Micro… and even for AWS Lambda!
The first thing we need, it’s defining our own local schema. We won’t define a schema for all remaining entities that commercetools doesn’t provide support in its GraphQL API.
To simplify our example, we will develop the schema corresponding to the customer entity, but you can follow the same approach for the rest of the entities.
A GraphQL schema is the union of two parts: the schema definition (types), and the resolvers (functions that provide the actual value to each field defined in the type)
So let’s start by defining the necessary schema types for the Customer entity.
I have created a separate file for the Address type since this type is reused in other entities such as Orders and Carts, so if you implement those types you just have to import the Address type and it’s not necessary to repeat the Address type for each type that requires an Address.
The only important thing to point out in the previous files is that the Customer type has to export the Address type too. The idea is to export all the types the Customer depends on, in order to not forget to include a dependency.
You can find more information on how to modularize your schema in the following link
Once we have our Customer type, we need to create our resolvers for those types.
Basically, the resolver is a function that “resolves” the actual value of each field within the type.
The fields with scalar types (String, Int, Float, Boolean, ID) are automatically resolved if the object that we are resolving has a property with the same name as the field in the type.
For complex types, we have to implement the resolver function that actually returns the corresponding value.
We see that for each property in the Query (customer) and Mutation (updateCustomer, changeCustomerPassword, saveCustomerAddress, removeCustomerAddress), we have its corresponding resolver, which, in turn, returns a Customer object that is also resolved by its corresponding resolver.
Since the scalar properties such as “firstName”, “lastName” and so on… are resolved by default, we just have to provide the resolvers of the Addresses properties (defaultShippingAddress, defaultBillingAddress, …).
Note: “customersService” and “authUser” are passed as properties in the resolver context. It’s a way to inject the dependencies (DI) in your resolver.
We will see later how these dependencies are injected.
The “authUser” is the user that is making the request, so we can validate if the user is authorized or not in the underlying business layers. To learn more about authorization in GraphQL, I highly recommend this article from Jonas Helfer.
I leave the “customersService” implementation because this service depends on your business layer, so it can end up being a call to a database, a call to another API, etc… It’s just a service that given the request parameters returns the required data. In this particular example, it will call the commercetools REST API.
With the types and resolvers already defined, it’s time to create our Customer schema.
Pretty simple, right? 🙂
We already have our local schema (see the diagram at the top), and now we need to create our remote schema corresponding to the commercetools GraphQL schema.
In this remote schema, as opposed to our local schema, we only have to set the schema definition (types), because the resolvers are already implemented by commercetools.
If a request includes a property of a commercetools schema type, our server will work as a proxy, forwarding the request to the commercetools Graphql API, delegating the action of resolving the property to it.
The schema definition of a remote schema is retrieved running an introspection query.
This introspection query can be run each time we start the server up, or we can run it once and store the schema definition in a local .grapqhl file. It’s considered a good practice to use the latest option.
There is a library called “get-graphql-schema” that helps us to get a GraphQL schema and store it in different formats.
Just install it
$ npm install -g get-graphql-schema
And run the following command:
$ get-graphql-schema [OPTIONS] ENDPOINT_URL > schema.graphql
In our particular case, since commercetools requires an access token to run a GraphQL query, the command is
$ get-graphql-schema --header "Authorization=Bearer <token>“ https://api.commercetools.co/<projectKey>/graphql > commercetools.types.graphql
where <token> is an access token that you can easily get, following the documentation and the <projectKey> is the key of your commercetools project.
The resulting file contains the schema definition in IDL syntax but it’s easy to convert to a String enclosing all types by the back-tick (` `) and saving as .js file.
I didn’t paste the whole file because the commercetools schema definition is huge.
With the commercetools types stored, we can create the commercetools remote schema (remember that the resolvers are delegated to commercetools).
The previous file has several things worthwhile to mention:
- It has a “commercetools” service injected. This service provides various values that are necessary to properly forward the requests to commercetools such as the api host, the project key, and the access token
- It includes the “http link”, that will be responsible for fetching the data from commercetools
- It also imports the “apollo-link-context”. This library provides a “setContext” function that allows us to add the authorization header in each request made to commercetools
- It creates the executable schema from the schema definition we have stored previously, and together with the “http link”, we get the remote schema
At this stage, we have both schemas that we want to merge. Our local customer schema and the remote schema.
In the following file is where the schema stitching occurs by merging both schemas into a single schema thanks to the “mergeSchemas” function.
Finally, the last remaining step is to import our new schema and expose our GraphQL endpoint using our Express server and the “graphqlExpress” middleware.
If you remember, we injected the “customersService” and “authUser” properties into the resolvers of our local schema. It is by means of the “graphqlExpress” middleware how we inject those properties in the resolver context.
The “authUser” is the decrypted user included in the JWT token that comes in the authorization header.
You don’t need to add the JWT middleware if you don’t want to add authentication/authorization in your API. In our case, I have added the middleware since it’s a pretty common scenario to secure your API.
Our GraphQL API also exposes an endpoint for the GraphiQL tool in the “/graphiql” uri.
In the following image, we can see a single query running against our single GraphQL API, but behind the scenes is executing two requests: one to our local schema (customer), and the other one forwarded to the remote commercetools GraphQL API (categories).
This is my first article related to GraphQL. I hope you find interesting.
Subscribe to us here and follow us on Twitter for more awesome GraphQL stories & tutorials!