Your API Choice Could Make Or Break Your Company... GraphQL vs REST From a React Developers Perspective.

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 whenPropTypes became it’s 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.

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.


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:

Lets imagine we’ve just started creating an app that sends requests to our REST API. After we fire a request, whats 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.

  1. Lets 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 asfriends, but this time we’re smart enough to think ahead and also store them indexed byid.

// 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}],
...
}

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 userPosts 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.

How front enders feel every time they need to change the data model.

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.

  1. Ok, staying consistent with Step 1 from the REST section… We want an app that displays the current user's profile. Lets fetch the current user and their posts. 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 newfriends 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…

If you’ve gotten this far, you may be interested in future blog posts from me. This was my first post, and had a lot of fun making it, so I’m definitely planning on doing more tech writeups! If there’s something you’re particularly interested in, mention it in the comments and I’ll take a look.

Like what you read? Give Alek Hurst a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.