Server-side GraphQL Querying with Elixir Absinthe
By- January 29, 2019
GraphQL is a few years old, and its promises are well known and pretty compelling. Get only the data your front end needs to display, introspection and type constraints, relate all your data in a graph of relationships, etc.! All great things, but if you’re like us and you start to retrofit a GraphQL API onto a REST-based site, you start to notice a divide.
We decided to build GraphQL into our API scanner. Since we’ve built it using Phoenix in Elixir, Absinthe was our go-to choice for a GraphQL query engine. Where before we had a set of contexts and related queries to provide information for our views, now we also had our GraphQL schema defining relationships and queries for fetching particular sets out of the database. It’s not a huge increase in maintenance and overhead, but it does mean duplicating authorization checks and a few other concerns, like remembering to preload for particular edge cases. It would be nice if we could use our GraphQL interface on the server side, particularly if you want to, say, pre-render a single-page app… and it turns out with Absinthe, you can!
Let’s take, for example, a simple social media site. On the site, there are users and posts, where users can become friends and posts can be liked. In order to populate the initial view of this site, we would need to get the current user, preload their friends, preload the first N posts between their posts and their friends’ posts, and the likes on those posts. We'd also need to have a GraphQL query for that same information when the state changes for the current user (for instance when they scroll to the bottom of the page). This is a good amount of duplicate querying, but with Absinthe you can add the @graphql annotation before a method in your controller to query the same information that your front end would pull. The results of the query becomes the parameters map given to your controller. For instance:
Relatively compact, fairly convenient, and by nesting everything under the current user, we should be able to ensure they only access things they are allowed to see. However, it looks like we’re grabbing just about every field available on our (admittedly quite simple) GraphQL schema. Also this query will return a bare map, rather than the structs defined in our application (which could be useful to have elsewhere in the app). Absinthe comes to the rescue for both of these issues by providing a shortcut in the @graphql annotation. Given a query where a field is requested but none of its subfields are specified, it will grab all the fields and, in the case of a field backed by an Ecto schema, will use that struct instead of a bare map. From there, you can use @put inside that object to grab the associations you want to load. This leaves us with this fairly succinct query for our controller’s index action:
And there we go! Now this hypothetical app need only worry about one path for providing data to users, and can concentrate on authorization along the GraphQL path. As long as our graph is complete, we don’t need to worry about making new specific queries for our controllers. Happy coding!