Your API Choice Could Make Or Break Your Company: GraphQL vs REST From a React Developers Perspective
by Alek Hurst
React as a framework has remained pretty much the same since it stabilized in 2015. Yes, there are some new async methods, and at one point you had to refactor all of your React imports when PropTypes
became its own package, but these changes are pennies compared to the first server-side revolution since Ruby on Rails: GraphQL.
It may sound strange that one of the biggest changes to using a front-end framework is what API your back-end decides to create, but let me assure you that this is a massive decision and there is indeed a right choice in the debate of GraphQL vs REST.
I want to convince YOU to join the GraphQL army and kill REST for good.
Together we will save thousands, if not millions of engineering hours in the years to come.
Join The Discussion: CyberGRX TPCRM Community Discussion Board
GraphQL vs REST
Diving In
In this post, I’m going to demonstrate how you’d go about making an app with data coming from a REST API vs making the same app with a GraphQL API. This will surface the pain points that come up when using REST and how they are addressed by using GraphQL.
Note: the following is meant to be “library independent”. These concepts will apply no matter which libraries you use to support your API. For example: the same pros & cons are valid whether you’re using Redux/Flux/Context with REST or Apollo/Relay with GraphQL.
Working with a REST API:
Let’s imagine we’ve just started creating an app that sends requests to our REST API. After we fire a request, what’s the first thing we do with the response? Store the data. Whether we’re using Redux, Flux, or the new React context API, we have to make decisions about storing the response data in the best way possible. This works fine when we’re creating our first TODO application, but the moment our app begins to grow we’ll quickly be creating and maintaining an entire data model.
The creation of this app-specific data model will result in a brand new learning curve for every component that uses it. It will also lead to additional bugs due to the increased complexity we have to manage.
- Let’s get started with our app: we simply want a website that allows a user to look at their own profile to see their own posts. To do this, we get data from
/users/:id
and/users/:id/posts
. We might end up storing the data something like this:
// How we've decided to store data returned from `/users/:id` { currentUser: { id: 1, name: 'Alek Hurst', status: 'baller', }, currentUserLoading: false, currentUserFetchError: null }
// How we've decided to store data returned from `/users/:id/posts` { userPosts: [ { id: 1, content: 'Does this app work?' }, { id: 2, content: 'Ok cool'}, ... ], userPostsLoading: false, userPostsFetchError: null }
2. This works great! So great that our app is gaining popularity and is now freezing. This is because users are making lots of posts, and our <Post postId={id} />
component uses the following code in its render()
method:
const postToShow = userPosts.find(
post => post.id === this.props.postId
)
3. Ok, no problem, let’s solve this by creating an indexed version of userPosts
by id
.
userPostsById: { 1: { id: 1, content: 'Does this app work?', userId: 1 }, 2: { id: 2, content: 'Ok cool', userId: 1 }, ... }
4. Cool, that’s out of the way… Now we can now replace the .find()
with
const postToShow = userPostsById[this.props.postId]
5. Our users are so happy with this UX improvement that they are posting multiple times a day, but we notice they are constantly viewing the same set up profiles to stay up to date. I know the solution! Let’s create a friends feature.
6. Back end creates a new REST route for us:/users/:id/friends
.
7. We hit this route and store the results as friends
, but this time we’re smart enough to think ahead and also store them indexed by id
.
// How we've decided to store data returned from /users/:id/friends { friends: [ { id: 2, name: 'Barney' }, { id: 3, name: 'My Girlfriend' }, ... ], friendsById: { 1: { id: 2, name: 'Barney' }, 2: { id: 3, name: 'My Girlfriend' }, }, friendsLoading: false, friendsFetchError: null, }
8. Now that we have friends, we want to look at their profiles. Thankfully we‘ve already made the <Profile userId={id} />
component and are already using the route /users/:id/posts
for that data, so we don’t have to do any more work!
9. WRONG. We currently store all the results from any request to /users/:id/posts
in userPosts
, all of which are displayed on the profile page. This is no longer the case. When we fetch the profile data for our friends using /users/2/posts
and /users/3/posts
, it will all be added to userPosts
. This means that all of our friend’s posts
will be end up being displayed on everyone’s profile. We need a way to fix this efficiently. To do this, we add userPostsByUserId
to our data model:
userPostsByUserId: { 1: [ { id: 1, content: 'Does this app work?', userId: 1 }, { id: 2, content: 'Ok cool', userId: 1 }, ] 2: [{ id: 3, content: 'Hey kids Im Barney', userId: 2}], 3: [{ id: 4, content: 'Why r u on this dating app!?', userId: 3}], ... }
How front enders feel every time they need to change the data model.
10. At this point, we now need to go back and edit the <Profile userId={id} />
as well as any other React components that display posts from userpPosts
and tell them to only display posts from userPostsByUserId[:id]
. ← this is where bugs happen. Any components we miss will still appear to be working because they are still displaying posts… just not the right ones. If we’re lucky, a tester or product manager will find this bug before the users do.
I could go on and on, but even this small example is enough to prove how quickly the complexity grows when we need to manage our own data model. No matter how you do it, using REST means managing your own data model.
Working with a GraphQL API:
The most beautiful thing about GraphQL is that it is its own data model. A GraphQL schema (the file that describes the data model) is the byproduct of creating a GraphQL API. This means that if the API exists, so does it’s schema. The cool thing about the GraphQL schema is that it can be sent over to the client. In fact, when using a library like Apollo or Relay, the client must have this schema before it’s able to make any queries against the GraphQL API. This is FANTASTIC for front-enders because all of the data associations have already been established and set in stone.
- Ok, staying consistent with Step 1 from the REST section… We want an app that displays the current
user
‘s profile. Lets fetch the currentuser
and theirposts
. We create a<Profile userId={id} />
component that uses the following GraphQL query:
query { user(id: 1) { name, status, posts: { id, content, } } }
Libraries like Apollo and Relay take the response data from queries like this and store the data in their internal cache. After storing the response data, it’s handed to the component that asked for it. This is all done automatically, meaning we write 0 lines of data management code.
When using GraphQL, we write our React components as if the data is already there and, Yes, there are still loading & error states, but they’re generated by Apollo or Relay and are handed to us automatically.
3. At this point in the REST example, our app was freezing. To solve this, we had to manually store an index of posts
by id
to efficiently display data from a specific post… With GraphQL libraries, everything stored in the cache is indexed by a global id, so the <Post postId={id} />
component already fetches data efficiently with its original query:
query { post(id: 1): { id, content, } }
4. Your next thought might be “but this is a GraphQL query, won’t it have to fire another network request to populate the data in the <Post postId={id} />
component?”. Nope, the cache is smart enough to know that we previously fetched this post
when we made the query in step 1, so it hands it over from the cache without needing to re-fetch.
5. Time for the friends feature.
6. The back enders have generously added a friends
field underneath user
. We use it:
query { user(id: 1) { friends: { id, name, } } }
7. At this point in the REST app, we needed to show our friend
’s posts
on their profile. This required a whole bunch of refactoring to the front end data model. Since we are now using GraphQL, use the posts
field directly under our new friends
field:
query { user(id: 1) { posts: { id, content, }, friends: { id, posts: { id, content, } } } }
This means that all we need to display a friend
‘s profile is pass the friend
‘s id
into their <Profile userId={id} />
component.
Similarly, the <Post postId={id} />
component in Step 3 will already be able to handle fetching & displaying posts from our friends
, since all we need to hand it is apost
id
. No refactoring needed.
Tada! We have created the same functionality we had in our REST version and have written half the amount of code!
After using GraphQL:
- We have all of our data stored, associated, and indexed efficiently without writing ANY data management code.
- Our product works great and the team is happy that there are fewer bugs.
- As front enders, we are much more inclined to add new features without putting up a fuss.
Now, what are we going to do with all of this extra time on our hands…
Continue Reading: How To Create A Barebones Production Ready NPM Package With Babel 7
ALEK HURST
SENIOR ENGINEER
Original article published via Alek Hurst on Medium.
To learn more about how CyberGRX can help you manage your third-party cyber risk, request a demo today.