Whether you’re developing for a new startup, rearchitecting a huge codebase, or doing a side project, it’s important to figure out the API design. REST has been the standard for a long time. Since its introduction in 2015, GraphQL has been a great alternative. GraphQL might be the ideal choice depending on the size of your backend team, the number of platforms consuming the API, and the type of information needed by the platforms. At Anchorage, we’ve decided to use GraphQL, have enjoyed the benefits, and found interesting solutions to some of the downsides we ran into.
Now that we’ve been using GraphQL for about a year, below is a reflection on why we chose it, when GraphQL might be the right choice, and the pros and cons of using the language from an iOS perspective.
Why We Use GraphQL at Anchorage
We built our first mobile app prototype using a REST API with JSON serialization format. We decided to move to GraphQL for two main reasons. First, we had multiple clients (both web and mobile) that often required different information for the same features and we only wanted to build our server API once. Using REST, this would mean providing a superset of all the information needed by all clients just so that it’s partially discarded per client. Or, creating multiple endpoints, which can triple the work. Second, GraphQL’s explicit schema between clients and backend gave us a lot of benefits, which is explained in detail below.
When to Use GraphQL
GraphQL has some adoption costs, including the learning curve of the language and (in some cases) the need for a communication layer between the client and server.
We think GraphQL is particularly useful when multiple clients need to consume different parts of the same object. Does your codebase contain endpoints that return the entire object while each client needs only a small but different portion? Or do you need to create new endpoints that return different combinations of objects as new screens are being designed? GraphQL can help you avoid both scenarios and provide more flexibility.
However, if the backend team is small and the different clients display similar information using the same endpoints, the cost of getting started with GraphQL may not be worth the benefits. It’s important to note that GraphQL can be used on top of REST.
Below we discuss some of the pros and cons of GraphQL the mobile app has encountered.
Note: For all code snippets below, we’ve altered parameter names. The examples imagine a crypto wallet app in which the users can own multiple wallets, make transactions, and learn about different cryptocurrencies.
- Shared schema for all frontend platforms that is generated by backend.
Since we use type-safe languages on all platforms, this generated code guarantees explicit typing across the server and all clients. Nothing is ‘stringly typed’ as it is with JSON keys. As a result, we have eliminated a certain amount of confusion and/or back-and-forth during technical discussions and specifications as well as implementation.
We’ve also found generated enums to be very helpful (a field on a type can be an enum generated by the backend). The app can safely code for new enum cases before they’re added to the schema by dealing with defaults. Conversely, and more importantly, the backend can unblock frontend engineers by simply adding the new (but unimplemented) cases. We use this tactic often when adding new assets to our platform. The new asset enum case is added first, and backend and all frontend engineers parallelize implementing the new asset.
2. Custom queries (similar to GET in REST) without requiring additional backend help.
For us, the biggest benefit of GraphQL is that we can self-serve our own queries because we can often build features without backend changes. For example, the UI of our home screen has evolved numerous times in the last year, which included the type of information displayed. At one point, the display required information from three different resolvers (types). Each time, we were able to easily create the specific queries with the already made fragments without any backend changes. Fragments are repeated parts of the query logic that we’ve used to map to an object (ViewModel) in our codebase. For more information on implementing GraphQL on mobile, refer to their documentation.
3. Faster loading time and therefore a better user experience.
By fetching only fields that the screen needs, the overall message size decreases, resulting in faster loading times and a better user experience. This can also be a con (see below). In this example, the first screen shows a list of transactions. The second screen shows the transaction detail. Here are the two queries that fetch only fields needed:
Implementing pagination has been the same.
For mobile, implementing pagination has been the same between REST and GraphQL. With edges, cursor, hasNextPage, etc., we have all the functionality that we had with REST for pagination.
- Difficult to add dynamic headers on requests.
We currently use ApolloClient to help with fetching data via GraphQL (the communication layer mentioned above). When using REST API, which is built on top of the HTTP protocol, we added custom dynamic headers easily because adding dynamic headers with existing HTTP Client libraries was trivial. As we explored GraphQL and ApolloClient, we realized that ApolloClient has a default unauthenticated header with no easy way to add dynamic headers. We had to create a custom NetworkTransport to implement dynamic headers. This pattern is also useful for retries, background sessions, etc. Below is our custom NetworkTransport code:
2. Our mobile architecture is mainly Model-View-ViewModel (MVVM). The ViewModels handle most of the business logic. When we fetch fields that are only needed for that screen, each of the different queries uses a slightly different fragment, which ultimately leads to slightly different ViewModels. This can lead to repeated business logic.
Looking at the example above where one screen shows a list of transactions and the second screen shows the transaction detail, we see that name, asset, and state are fetched in both queries. Imagine these two interfaces below that might have to be repeated in both ViewModels. To reduce repeated code, we’ve created protocols that similar objects adopt. However, we still do have some repeated code, which is not ideal.
3. The final and the main con of using GraphQL has been the increase in build time. All the generated files add on to build time! One way we’ve reduced it was by splitting the schema into separate logical files based on ViewModels. Apollo code generation can split those into separate generated Swift files, but it still amounts to a lot of Swift code (400K LOC) that takes minutes to compile on the fastest Macs available!
Using GraphQL on mobile has been an overall positive experience for us. The ability to move quickly and create specific queries on our own has outweighed the cons. The two main challenges we face are avoiding repeated code while using ViewModels and maintaining a reasonable build time as we add more .graphql files. We are happy with the workarounds we have found/developed.
Services are offered either through Anchorage Hold LLC, a Delaware limited liability company and registered Money Services Business, or Anchorage Trust Company, a South Dakota-chartered trust company. Anchorage Hold and Anchorage Trust Company are not registered with the SEC. Services are not yet offered to residents of New York. Anchorage Hold and Anchorage Trust Company do not engage in the offer or sale of securities or digital assets, and do not provide legal, tax, or investment advice. Anchorage Hold LLC and Anchorage Trust Company are wholly-owned subsidiaries of Anchor Labs Inc., a Delaware corporation headquartered in San Francisco, California.