This year, I was fortunate enough to be able to attend React Europe in Paris. There were some great talks about GraphQL at the conference, and I think anyone excited about GraphQL should check them out on the React Europe YouTube channel.
So that you don’t have to watch all of them, I’m going to go through my favorites and summarize them in blog post form. Previously, I covered “GraphQL Future” by Laney Kuenzel & Lee Byron. Today, I’ll look at a talk by Dan Schafer about how Facebook organizes their GraphQL server to handle business logic and permissions.
To start, Dan went over how exciting it was that so many people at React Europe were familiar with GraphQL, just one year after it was open sourced at React Europe 2015.
Then, he gave a quick overview of the topics the talk would cover:
Dan went on to outline how Facebook thinks about application data:
- The data in an application is a graph. You can have many types of objects (posts, comments, etc), those objects have fields, and there are relationships between those objects, which form the edges of the graph. So while GraphQL is not a query language for graph databases, it’s still an appropriate name because it queries the graph of your application data.
- Single source of truth. You don’t want to reimplement data fetching, business logic, or security every time you build a new feature. That stuff should be solved exactly once for the types of data in an application.
- Thin API layer. The above leads to the idea that the API layer of the application shouldn’t drive how the business logic works. While GraphQL is a convenient way to fetch data, you don’t need to design your backend around it. Instead, just write your backend in the way that makes sense, and put the GraphQL API on top of it. This also means that you can change your API without messing with your business logic.
Let’s say we want to implement some simple authorization in a TodoMVC app:
Where should this permissions information/logic go? One of the first questions you can ask is, “What is a todo item?” Is it:
- The API endpoint you call to get that item? No, that’s too API specific, and we want the API to be a thin layer that we can change whenever.
- The SQL row that you read to get that item? No, that’s specific to the underlying storage engine, so this isn’t a good layer to have a single abstraction.
So it seems the permissions have to be somewhere in between. Dan calls this layer the “business logic”:
Essentially, the point is that at Facebook each type of data has its own model object, that might look something like this:
So now that there is a single source of truth for fetching the data — a single function that is always called when we need to get that object from the backend, we have a clean place to put some logic for authorization:
As a convention, Facebook’s model code always accepts an object called the viewer, which represents the entity currently asking for the data. This can contain the user object of the currently logged in user, any permissions roles that user has, and anything else that is necessary to determine permissions.
So we come back to the question:
How do you do authorization in GraphQL?
Well, just like this:
It turns out that once we have the above model code that already handles the permissions, we no longer have to worry about that at the API layer. All we need to do is pass in the appropriate viewer (this is often also called context in the open source versions of GraphQL) and information about which object to fetch.
This is another common question people have about GraphQL. If you can fetch so much data in one request, is it efficient?
To start, let’s take a look at a naively implemented GraphQL server handling a pretty basic query:
You can see that this isn’t a very efficient way to get the required data. For example, it’s often better to do a single batched operation instead of many small fetches. Given that our server needs to fetch 5 friend objects, it should be able to do so in one pass. Here’s what a more efficient set of fetches would look like for the same query:
So how do we implement batching in GraphQL? Well, just like above, it turns out there is nothing in this problem that is specific to GraphQL. You would want this batching no matter what your API looks like, if it allows you to fetch more than one item at once. So, just like before, the batching goes in the business logic layer.
Here’s what our code looks like with DataLoader:
With this new code, when we are resolving the GraphQL query, DataLoader will collect the list of IDs we need to fetch, and pass them to the batch function all at once. So now we’re doing much better:
But we can do even better! As you can see above highlighted in red, we are fetching users 1, 2, and 5 twice during the execution of the query, even though we already have them. This means we are incurring unnecessary load on our backend.
Thankfully, we can solve this with a very simple caching approach. It turns out, if we already fetched an object with a certain ID in a request, we can just save that and reuse it instead of actually sending a new request. And, what’s better, DataLoader does this by default. It looks at the IDs, and if that ID has already been passed before it simply uses the previous result. So in fact with DataLoader our logs look like this:
Not bad! That’s a pretty efficient way to fetch the data we asked for.
Efficiency: Caching on the client
With HTTP caching, you can just cache the result of the URL we fetched. But that’s not so useful with GraphQL, because we call the same URL over and over with different GraphQL queries, and get different results. Moreover, caching using the GraphQL query string isn’t that efficient, because our queries might overlap, meaning we might be storing the same objects multiple times.
Since we’re already using an ID for caching on the server, why don’t we just return that ID to the client?
But we can end up in an annoying situation, as described on this slide:
We have IDs, but the first one refers to a user, and the second is a photo. Of course, we don’t want these to be cached as the same object. So it’s convenient to have IDs be globally unique across all types of data. That way we can just use the ID as-is to identify objects.
Next, Dan goes over some other desirable qualities for IDs:
Once you have a globally unique ID, and the ability to get that from the backend using the ID, you can use it as a refetch identifier. That way, you can refetch any object you are currently looking at.
Then, one more question is — do we want the client to be able to interpret the ID (for example, if it’s a combination of the SQL ID and the type name). Dan believes that it’s an anti-pattern to have the client be aware of what the ID represents — it should just be an opaque identifier that refers to that object. This is the best way to hide server implementation details from the client, for example if you need to change the backend store and ID semantics change.
So now, our result looks like this:
So, to summarize the main principles from the talk:
At the end, Dan mentions that you can learn more at graphql.org.
Editorial — My thoughts on the talk
I love that these ideas are not GraphQL-specific. Working with GraphQL can often feel so different that it seems like everything needs to be reinvented for this new API technology. But I think the idea that any API should just be a thin layer on top of the actual business logic is really great — if you follow this, you can write your business logic once and expose it over GraphQL, REST, Falcor, and anything else you need, depending on the application.
I think there’s a great comparison to Rails here. When beginners start working with Ruby on Rails, the first temptation is to put everything into the controllers. Since that’s the place that actually handles the requests, it’s tempting to put all of the business logic in there, and just use the default model calls. But many Rails guides and books suggest keeping the controllers as simple as possible, and doing all of the logic in the model code. It turns out the same principle applies to GraphQL. So while the code looks a bit different, a lot of the concepts can easily transfer over.
GraphQL can be even more efficient than REST. When building your client with a REST API, you can often end up calling endpoints that fetch overlapping data. In a REST server, there might not be an easy way to do caching across these endpoints. Since the GraphQL server receives the whole query in one request, it can more easily do batching and caching across different fields, meaning that in many cases an equivalent GraphQL query will be much faster than the corresponding set of REST calls for the same data.
We believe strongly in these principles in Apollo. We’re still working on better documentation and guidance about how to organize GraphQL servers, but the approach with models and business logic is a very clean way to do it. Read more about how to organize your GraphQL server in our previous post, “How to Build GraphQL Servers.”
I think sometimes it makes sense for GraphQL IDs to not be opaque or globally unique. In the Facebook architecture, GraphQL completely hides the backend implementation. When you have this really strict separation of concerns it is nice to have opaque IDs so that you can switch out the underlying implementation.
But I think it depends on the use case. For example, if you have a hybrid approach where some of your data is coming in over GraphQL and something else is an external API or a websocket, it can be advantageous to have access to the original (non-unique) ID of an object. Moreover, many REST APIs currently use and return sequential SQL IDs, so if you think of GraphQL as the thinnest possible API layer, it makes sense to simply pass this through.
This is why in Apollo Client we allow the client to pick how it uses IDs, and one choice is to use the id and __typename fields together as the cache key, as you can see in the example app. Our main reasoning behind this is that it should be as simple as possible to start taking advantage of GraphQL’s main strengths, and it shouldn’t be necessary to change the way you think about IDs.
How do your organize your API layer and business logic? Drop me a line in a response here on Medium or on Twitter, and let’s talk.
Follow us on Medium to get more in-depth content about how to build stuff with GraphQL, and news about the GraphQL community. Oh, and one more thing — get hyped for the new #GraphQL podcast with Abhi Aiyer, with the first episode featuring Scaphold, coming soon!
Stay in our orbit!
Become an Apollo insider and get first access to new features, best practices, and community events. Oh, and no junk mail. Ever.
Make this article better!
Was this post helpful? Have suggestions? Consider so we can improve it for future readers ✨.