How GraphQL taught me to code client apps
A few months ago, I began building a cross-platform application. I built applications before; most were just some ad-hoc endpoints and server-rendered web applications. I heard about REST, saw some great tooling, and read blogs and books like Restful Web APIs. I thought I was golden.
A nice REST architecture would keep me sane. A browser navigable API provides an excellent starting point for client app developers… or so I believed. “Sure, this is just SQL for URLs but everyone knows relational databases. Now we can start making a beautiful data-driven application!”
This could not have been more untrue. One of my team members had plenty of experience on iOS, but had little to no experience with databases. He would ask me “Why are we separating everything with links? I just want some nested XML/JSON.” I would answer with all my criticisms of ad-hoc endpoints and custom response payloads. After writing so many fragile and undocumented web apps, I did not want to revisit that life. My responses were like my country’s politics — indirect and ignorant of the issue.
I delved further into REST. I took comfort in the promises of machine-discoverability, HATEOAS, and dynamic link values. My API could avoid many breaking changes! What client dev wouldn’t want that? I would update my iOS guy on all this and, in hindsight, none of these points mattered much to his client app.
I was not yet writing a client, so developing an API first made me less understanding. My first mistake that GraphQL’s release has taught me: Don’t build your API first. You do not know what your client will want ahead of time.
Then I started coding the Android implementation. Excellent API interfaces from tools like Retrofit gave me solace. But, I could not help feeling a gross twinge about two main concerns:
- I just want some object graphs
- Why am I making so many round trips?
Rx Observables forced my code to avoid callbacks and gave it a declarative feel. Concerns 1 and 2 seemed unimportant as I distracted myself with the Android ecosystem.
After a three week holiday, I came back to discover the front-end had changed at breakneck speed. React, Redux, time-traveling debugging, Flux… I had a lot of reading to do.
I implemented my front-end web in React + Redux, inspired by Dan Abramov’s React Hot Loader talk. My UI became easy to reason about. My application became a composition of logical components. The component tree is a powerful concept. Immutable properties and a single state tree got me close to answering concern 1. All I needed to do now was write reducers to update my global object graph!
Pagination, the many round trips, this made everything in a React lifecycle hook awful. I knew I was being picky, this was more clean than apps I used to write but why settle for the following:
Those booleans were tripping me up. It broke abstraction. The app should be a thin view over a state machine. All these booleans existing outside of actions clouded that model. It no longer became clear what state the app was in just by glancing at the code.
Was I going to start writing ad-hoc endpoints just for my client application? For just these individual views? I spent most of my time data-munging; I was not building useful components or making my app pretty.
Engineering is hard when you don’t have the right abstractions. Why is React so easy to use? It takes the imperative DOM manipulation out of the equation. Thank you, React. But where was this solution for my schema?
Enter GraphQL upon the open sourcing of Relay.
I toyed with it, watched my most hated block of code disappear. Realized React-Native was a thing. Oh god, now I could do the same on mobile. No longer did I specify how my API should get me its entities, I told that machine what I wanted with GraphQL.
I can illustrate this with the porting of the above gist to this Relay/GraphQL code.
The above is much longer since each resource now gets its own component based on GraphQL Fragments. Each component will only update when the part of the query that is passed into its props is altered. Pagination is handled for you with GraphQL 😍.
Redux has this same functionality if you wisely use its @connect decorator. GraphQL allows me to use the same declarative style for API interactions.
And in the end, I’ve conceded that Facebook’s engineering teams have killed it. @samerbuna said it best: