AppSync is a serverless GraphQL service on AWS. GraphQL uses a strongly-typed API definition that serves as a contract1 between the client and the server. Adhering to this schema helps both frontend and backend teams build simultaneously and deliver faster. However, it is easy to make accidental, breaking changes to your GraphQL schema (I've done it; every one of my GraphQL engineers has done it; you will do it). And some breaking schema changes may not trip your functional, end-to-end tests (e.g., making a parameter required or a return type optional). Further, ensuring your contracts will be a key milestone on your journey to continuous deployment.
Fortunately, we have tooling available to help us detect breaking GraphQL schema changes. My favorite is GraphQL Inspector. It can be used in a variety of ways (GitHub Actions, CLI) but today, I want to show you how I use the GraphQL Inspector TypeScript library to do a pre-deploy schema check via Jest.
GraphQL Inspector's diff()
function takes two schemas: your old (existing) schema and your new (soon-to-be-deployed) schema. Both need to be typed as GraphQLSchema
and this is easily handled with GraphQL.js and its buildSchema()
function. I have links to working examples at the end of this post. Now we just need the two schemas to compare - how should we get them?
Let's tackle the easy one first. Your new schema is sitting in front of you on disk. In my case, my soon-to-be-deployed Orders schema is at orders/schema.api.graphql
. After a simple fs.readFile()
, I only need to invoke buildSchema()
to have my new schema diff input.
We have at least a couple of options for finding the "old/existing" schema: pull from source control or pull from your running AppSync service. I prefer the second option, as what is in source control may also be a failing, broken schema from someone else. If this test acts as one of your gates to deployment, then you can count on the deployed schema being "last known good." Pulling your existing schema from AppSync involves a single AWS-SDK call like so:
All we need is the ApiId
and to be running in a context with sufficient permissions to introspect our AppSync API. Once we have our existing schema text, a quick buildSchema()
gives us our final input for GraphQL Inspector's diff()
.
I added a helper function, hasBreakingChanges()
, to detect any changes labeled "BREAKING" by the Inspector. I run this test before I begin any deployments and it has saved my bacon on numerous occasions.
Sometimes you are working on a new feature and the schema has not yet solidified. In this situation, you'll be pushing broken changes often. During these periods I will skip
this schema test and announce (loudly) to the teams working on this module that I have done so. Be sure to leave a note for yourself to turn it on again. For example, our team chat channel will have two messages about this. The first says "Orders schema check OFF for new feature XYZ" and then, a little later, one says "Orders schema check ON". If I forget the second announcement, someone will invariably ping me and politely ask "Did you mean to leave the Orders schema check off?"
That's all there is to it. Try it out for yourself and see if you can find some value in the approach. Remember, honoring your contracts is a key milestone on your journey to continuous deployment. Move fast. Have fun!