Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lucasreta/0629515eda25a88f399d385821e5521e to your computer and use it in GitHub Desktop.
Save lucasreta/0629515eda25a88f399d385821e5521e to your computer and use it in GitHub Desktop.
Comparison between GraphQL clients

Client-side GraphQL

graphql_ppx

Let's first analyze how to write the queries. I experimented the graphql_ppx with a few different features (nullable and non-nullable variables, fragments, convert response into record automatically). The type safety is very neat, both the input variables and the response have specific types (no Js.Json.t!), and the queries/mutations are validated against the schema! This makes it really easy to write queries, although I worry about integrating new versions of the schema since the repos are separated (we should probably re-fetch the schema as part of the CI).

As for the drawbacks, there are not many:

  • The ppx really messes up my language server sometimes.
  • I haven't tried a lot but I couldn't find easily a way to use refmt to format the queries.
  • I couldn't find anything about custom directives, the lack of which would prevents us from using some features from the GraphQL clients (for example, managing local state using Apollo's @client directive).
  • I haven't hit problems writing the fragments and because of DDD we probably won't have many fragments anyway, but it's worth mentioning that its support is limited.

Overall, I think it's a really great option for writing the queries, much better than storing them as strings. We may run into some problems in the future if we require more complex features though.

Plain fetch

It's trivial to imlement a GraphQL client using plain fetch client due to helpers created by graphql_ppx to serialize the query/variables and parse the response. It's not a bad option for starting, and by using React hooks it can be easily replaced. Of course if we need any more advanced features (such as automatically re-fetching queries after mutations, caching, batching, deduping, etc.), it's better to use a library.

URQL vs Apollo

Apollo was introduced as a response to Relay Classic, Facebook's GraphQL client which enforced a highly opinionated architecture, had a very complex setup and didn't have a lot of exchanges with the community. Apollo Client in the other hand doesn't make a lot of assumptions about your queries and it is very flexible. Its cache is based on normalizing objects (by default compounding id and __typename). There are multiple, composable parts to the Apollo environment (server, tracing, schema versioning, etc.), and it has a huge community. I has first-party support for Reason.

URQL was introduced as a response to Apollo's growing complexity of setup, which was mitigated afterwards with Apollo Boost (which comes with a default configuration without more advanced features, allowing for customization when needed). Its cache is based on hashing the entire query + variables, and it has first-party support for Reason.

Here follows a comparison between the two options:

--- Apollo Client URQL
Cache Normalized objects Hashing query + variables
Batching With apollo-link-batch-http (although it recommends deferring batching as long as possible) Doesn't have a first-party solution but allows to use Apollo's Link extensions
Deduping With apollo-link-dedup (enabled by default) With dedupExchange
Authentication Supports adding options to the fetch client or changing the network layer altogether Supports adding options to the fetch client or changing the network layer altogether
Pagination First-party support with fetchMore, also provides several recipes No first-party support, needs to implement a custom solution
React Hooks No official support yet, seems to be waiting for Suspense to become stable (they recommend using react-apollo-hooks for now) First-party support
Optimistic update mutate({ optimisticResponse }) (requires manipulating the cache if inserting new data) No support
Local state Support with @client directive No apparent support (I've found no mentions to this feature)
Refetch after mutation mutate({ refetchQueries }) Needs to manually call a function obtained when performing the query
Subscriptions Supports Supports
Community Vibrant, easy to find answers online, official chat, huge number of issues and PRs Almost non-existent
Documentation Very thorough, with several tutorials and recipes Comprehensive

Reason support

Both options have first-party support for Reason, however not everything is implemented in neither.

Apollo has reason-apollo, which doesn't implement all features from Apollo Client. In particular I couldn't make the optimistic update work although it does have the optimisticResponse as a labelled argument to the mutate function. I get and error saying that I need to enable the addTypename configuration because I have fragments in my query, but this option is not available to the client. I tried removing the fragments but then I need to manually add the __typename to all queries. The refetchQueries also doesn't work by passing existing queries, instead we need to pass the name of a query as a string, which is very error-prone. There's no update argument to the mutate function, which prevents us from manipulating the cache for optimistic updates that create new records. There's very limited third-party support for hooks with reason-apollo-hooks. Apollo also makes heavy use of custom directives for more advanced features, such as local state management, so due to the limitations of graphql_ppx we wouldn't be able to make use of them. It's also worth mentioning that support for JSX3 is still in alpha.

URQL has reason-urql, which is also a bit limited. It doesn't implement the hooks present in the original package, and there's no working branch for supporting JSX3. There are less incompatibilies because URQL itself has less features than Apollo.

Conclusion

Apollo seems to me the clear winner. It's flexible and easy to setup, has advanced features, a great community and several other products in their platform. Considering that React Hooks was officially released in the beginning of February, it's kinda disappointing that there's no support yet, but that can be mitigated by using an external library (although it's very probable that the interface will be different).

The major drawback of adopting Apollo is the limited support for Reason. It's not possible to use several advanced features, and the reason-apollo seems to move very slowly. Most of what's currently possible is not hard to implement using a plain fetch. If we choose to use Apollo we'll probably need to write several custom bindings ourselves, and I'm really not sure what can be done about the custom directives issue.

That said, my opinion is that we should aim to wrap all querying/mutating logic in custom hooks anyway, so as long as it's possible to use the client with hooks, it shouldn't matter for the components that consume those hooks what's under the hood.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment