A lot has been written about GraphQL Schema Merging already - but in this post I want to share how we use it within Nivoda and the advantages that we have found by using it.
We have three distinct user levels on our platform: buyers, sellers and internal users (called admins).
If we take the example of querying for orders, you can imagine that each user level could have different responses for e.g. order status, and that admins need to have access to more information than buyers or sellers can.
We also have to take into consideration that all 3 user types need to be authenticated in a similar way.
For this reason, we started out by setting up 4 GraphQL schemas:
All of our authentication logic is defined in the Shared schema, and user level specific queries and mutations are defined in their respective schema’s.
The authenticated user information is made accessible through `context` - so each schema has access to the logged in user, and has additional top level checks to make sure the role of the user is the one we allow.
Here is how we merge the resolvers and schema below, by using Lodash’s merge function and makeExecutableSchema from graphql-tools.
Note that inheritance works from top to bottom - the schema and resolvers mentioned at the bottom will overwrite the ones that are already present at the top.
We have experienced a couple of great advantages by using this approach.
For example, it has become very easy to extend Type’s with fields that should only be accessible for a specific user type.
In this specific example, the person who is responsible for handling the order internally is not exposed to the buyer schema, but is part of the internal schema.
Another advantage is that buyers and suppliers have different statuses for OrderItems, which is easily solvable by merging the schema’s.
A buyer considers the status of an item to be “Delivered” when they have received it themselves, but for a seller, the status should be Delivered whenever they have delivered it to a Nivoda Office.
This is simply achieved by having the `status` resolver on *both* the schema’s, and resolving accordingly. Because Seller overwrites the resolver from the Buyer schema, this works flawlessly:
We have this setup with a lot of different fields and this makes it extremely easy to implement the API on the front-end, because we don’t have to include and fetch different fields based on the user type - all that logic is handled on the backend.
Because of this approach, we are also able to programmatically generate unique API endpoints for some of our customers, because they just inherit the shared schema.
This also allows us to modify the mutations or responses for each customer easily, as some like to work with different field names compared to what we use by default.
To summarize, we have been really happy with the flexibility schema merging has given us. We’ll continue to share our learnings and experiences here on the Nivoda Engineering blog!