No Graph Theory Required: Ember and GraphQL in Practice
Update November 12th, 2018: Video of the talk has been uploaded to YouTube by the EmberFest team.
Update October 30th, 2018: You can find the slides from our talk here: https://www.slideshare.net/RockyNeurock/no-graph-theory-required-ember-and-graphql-in-practice
We’ve been working with GraphQL in our Ember apps for some time now. I wanted to share our journey so others thinking about GraphQL, or struggling with JSON API, could gain a solid understanding of how to get started.
The post was adapted from a talk given at EmberFest 2018 by myself and my colleague Chad Carbert. Chad created an example repo that includes an Ember app demonstrating GraphQL integration with 4 different clients.
At kloeckner.i, we’re fortunate to have the freedom to experiment and try out new technology. The first time I saw GraphQL, I immediately wanted to try it. To me, it seemed like a better way to wrangle data from an API.
The real catalyst for our venture into GraphQL was the need to build new apps and services. We encountered some issues working with JSON API and it seemed like a great time to try something different.
Issues with JSON API
If you use Ember Data, you’re likely familiar with the JSON API spec for building APIs. It offers the path of least resistance with Ember Data and that’s entirely by design.
In my experience, it really does save tons of time with API design and provides a huge boost to your team’s productivity. That being said, it’s not without its pain points.
I’ll skip the details of CRUD in JSON API, assuming you’re familiar. What I do want to highlight is that CRUD doesn’t cover everything.
A real-world example of this from our own services is a shopping cart. To perform a checkout, the frontend updates a cart record (i.e., it sends a PATCH request) and we introduce some side effects on the backend to put the cart into a different state.
This all works fine but it’s completely un-obvious that this behavior even exists. Basically, communication with an API doesn’t always want to work with the CRUD pattern.
Ember developers are really fortunate to have Ember Data. For backend developers; though, things aren’t so easy.
Libraries can be difficult to work with or simply don’t exist for some platforms. Some of our fellow developers have said they suspect this might be due to the design of the spec itself.
In any case, dealing with the complexities of the spec that aren’t handled by a library can have major drawbacks. At worst, we’ve resorted to offloading some data handling to our Ember apps, which is obviously not ideal.
At other times, this issue has caused a lot of frustration writing custom code to handle those complicated parts of the spec. This is especially true when the actual task at hand is just a simple concept but the implementation causes a big headache.
JSON API Lags Behind GraphQL in Some Areas
I’ll borrow some details from a tracking issue Yehuda opened on GitHub: Tracking Issue for deficiencies vs. GraphQL #1272. Here, he perfectly summarizes some additional issues we’ve experienced with JSON API.
I’ve seen Ember developers gravitate the most toward his last two points:
- A built in way to describe types or having schema.
- Ready made tools for API exploration.
These things have a huge impact on productivity. But his first two points have a significant impact on the actual code that I write:
- Deep querying.
- Really flexible mutations.
I’ll show some examples of this later on.
To quote one of our developers:
When using GraphQL, I don’t feel like I need to bend it to my needs like JSON API.
— Thiago Massa
I agree with him and I think this is a really nice summation of our overall experience, having used both.
What is GraphQL?
In short GraphQL is a query language that can be used to retrieve data from your API. To retrieve this data from your API you can use a query that reads quite naturally.
In this example let’s imagine we’re online shopping. For our online cart example we want to pull the total amount, and for each item we would like the description and price. One of the benefits to querying with GraphQL is that you only request the data you need.
One of the interesting things about GraphQL is that your data’s relationship is represented by fields and types. This makes it easy to trace your data’s relationships and its connections all the way to the root nodes that describe what base queries and mutations you have available. This discovery extends to the tools, making it easier for tooling to infer the graph of data through an introspection query.
GraphQL has many language features and it is quite well documented on the GraphQL website, so it’s definitely worth taking a look.
Integrating GraphQL with Ember
I’ll cover some clients and then provide some examples of common patterns from our codebases.
After trying a few clients, we ultimately settled on Apollo. Apollo is a large scale GraphQL platform that spans the client side, server side and in-between.
While Apollo includes quite a lot of features and tooling, the ember-apollo-client addon makes working with them really easy. We recommend this addon and the examples provided demonstrate its usage.
In addition to Apollo, the first client we tried worked with Ember Data.
We didn’t choose this one.
At first, it was appealing because we were trying this in an existing Ember app and it allowed for the simple inclusion of a new adapter and serializer to get up and running.
As our GraphQL services developed, we found it too inflexible for our needs. It also limited some of the flexibility GraphQL provides and we didn’t think it would work at full scale.
These lightweight clients are worth mentioning.
They’re easy to include with ember-auto-import and Chad demonstrates their usage in the example repo he created.
Common Patterns in Our Codebases with GraphQL
I’m going to use the example of a shopping cart that’s very similar to the ones we build in our real-world Ember apps. This shopping cart can contain 10s of items so we paginate them when displaying the cart to the user.
This works fine but you don’t have control over the items you get. It would be nice to sort and paginate the items. There’s no way to do that with this code so we ended up with a lot of code that looks something like this:
This also works but it’s starting to get messy. There are also some other minor drawbacks:
- In this example, we have to make two synchronous network requests.
- The data structure of the model becomes a little unexpected.
We can get the same data with a GraphQL query.
Some of the things I really like about the GraphQL version, in no particular order:
- We have much more control over the data we get.
- The model code is reduced and easier for me to parse.
- We only have to make one network request.
- The order data remains nested.
Let’s expand on our example and say the user is allowed to update the shipping address for their cart items.
They can update the address for an individual item or they can apply one address to all the cart items. It’s the latter case we want to dive into.
First, let’s talk about how that might be difficult with Ember Data and JSON API. As far as I’m aware, both Ember Data and JSON API require data changes on a per record basis. I.E., you can’t arbitrarily update many records at once.
How could we perform this task with Ember Data? We need to update the cart items individually.
This isn’t bad but there are two things that stand out to me:
- If our items are paginated, we would need to load all of them in order to update them.
- We update the records in Ember Data’s store before we save them.
If the data is bound to a view, we end up with an optimistic user experience. This isn’t necessarily a bad thing but if anything goes wrong in the process, the records become out-of-sync with the server.
I don’t like dealing with attribute rollbacks or trying to communicate the problem effectively to the user.
Here you can see we don’t need to mutate any objects in our store. This means we didn’t have to load all the records to make the changes.
Since we didn’t update the records in our store, we avoid any out-of-sync UI in case something went wrong.
There are a few things I really like about mutating data with GraphQL:
- Mutations are extremely flexible. You can update any number, and types, of records at once.
- The data flow is more unidirectional.
It’s possible to be very irresponsible with such flexibility but in my experience, that just doesn’t happen. Because you can create exactly what you need, you do, and everyone benefits.
In a typical REST application you need to send a separate request, for separate resources on separate endpoints. This idea paired extremely well with mocking during acceptance tests as we’ve seen with `ember-cli-mirage`. With mirage you are able to stub a single endpoint and pass the the request over to a handler which has access to an in-memory database and factories.
When looking at an application using a GraphQL API the setup is slightly different. A query is sent via `POST`, with all queries sharing the same endpoint. This makes it more difficult to stub the graph endpoint with the correct data as the context depends specifically on the query itself, not the URL.
With an existing codebase that is migrating to use GraphQL it is not just the API requests that needs to migrated, acceptance tests also need to be considered. At kloeckner we had an existing codebase with `ember-cli-mirage` and it made sense to try to bridge the gap with GraphQL. This lead to the creation of `ember-cli-mirage-graphql` which provides a handler that maps a GraphQL schema to a Mirage schema.
Another testing tool that is relatively new that helps with API testing is Polly.js. Developed at Netflix, Polly is similar in spirit to the VCR gem used by the Ruby community. Polly requires that you have a “Real API” endpoint that your tests can use. This “Real API” is only used the first time while tests are running so that it can create a recording json of the request and the response. On subsequent test runs these recordings are replayed against matching requests, without the need of the “Real API” running. This works great with GraphQL since matching the request is done against the entire request, including the query.
Lastly if there is a specific use-case that needs to be handled in a custom way there is `graphql-tools`, by Apollo. This package allows unpacking a query, when a schema is provided, to handle the data with mock resolvers. With mock resolver functions there is the chance to handle on a per-type basis any part of the schema. This is extremely flexible but leaves much of the wiring up to you.
The testing method that is chosen ultimately depends on your use-case and your current environment, but hopefully one of these techniques will help provide some ideas on how to test your app.
Our Experience So Far
Our experience working with GraphQL and Ember has been really nice.
With an easy-to-explore schema in our hands, we know everything we can and can’t do. This greatly increases our productivity, eliminating tedious API discovery processes.
GraphQL’s flexibility allows us to design APIs exactly as we need them. We don’t have to shoehorn anything into a constrained API model.
The data flow changes have improved our user experience. This part is subjective, and depends on your desired UX, but it’s been really nice for us.
Thank you for reading. Hopefully you have a better sense of how to get started with GraphQL in Ember and what problems it might solve for you.
This blog post and kloeckner.i are unaffiliated with the Ember, GraphQL and Apollo GraphQL projects. Ember is a trademark of Tilde Inc.