Logo / QR code

How I’ve Built My First React Application III

Part 3: Fetching data with GraphQL like a pro

This is the third article of my "How I’ve Built My First React Application" series, showing the steps I’ve taken trying to build an isomorphic voting application using React. All the code is available at my GitHub repo: question-it.


Almost any application requires data from a remote data source, whether maintained by us or not. Here, I’m going to cover how you can accomplish such a thing in React applications.

Normally, React requires no special setup for fetching data. You could use fetch or XMLHttpRequest, as you would usually do in any other JavaScript code. But usually, when we need to fetch data, we also need to store and manage it, so we could optimize our application (with caching, for instance).

That’s why I used GraphQL and Relay, which I’ll introduce next along with some popular alternative(s).

Delivering Data: GraphQL

Before I’ll write about GraphQL and why I chose to use it, I’ll write about the most common alternatives: SOAP and REST.

REST API

SOAP (Simple Object Access Protocol), is a protocol for creating web services. SOAP web services use XML messages to exchange information and expose themselves via a service interface. Although it is old and primitive, SOAP is still being used to this day, since it also supports stateful operations and binds a contract between the client and the server.

REST (REpresentational State Transfer) is an architectural style of creating web services, which uses URI endpoints to expose its’ business logic and exchange information. REST is simpler to maintain than SOAP, and is not bound to the XML format, which makes it more popular amongst developers than its’ older brother SOAP. That’s why I’ll use REST to explain why I chose GraphQL over it.

GraphQL Logo
GraphQL

GraphQL (Graph Query Language) is a query language created and used by Facebook. GraphQL is used to build a strongly-typed, hierarchical API. By many, including myself, GraphQL is believed to be the successor of REST.

To create a GraphQL endpoint you define a schema. The schema will contain the root query endpoint, which will contain the subqueries as fields, and the root mutation endpoint, which will contain the sub mutations as fields.

Queries are basically read-only operations and can be compared to GET requests in REST. Although queries are read-only operations, they are sent via a POST request, as any other GraphQL request.

Mutations are the write operations and can be compared to POST, PUT and DELETE requests in REST. They are called mutations because they mutate the data on the server. That’s also why mutations are processed sequentially, while queries are allowed to be processed in parallel.

Here’s an example of a GraphQL schema definition:

import {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLID,
  GraphQLNonNull,
  GraphQLString,
  GraphQLList,
} from 'graphql';
const GraphQLUser = new GraphQLObjectType({
  name: 'User',
  description: 'Registered user',
  fields: (() => ({
    id: {
      type: new GraphQLNonNull(GraphQLID),
    },
    username: {
      type: new GraphQLNonNull(GraphQLString),
    },
    email: {
      type: GraphQLString,
    },
    friends: {
      type: new GraphQLList(GraphQLUser),
    },
    ...
  })),
});
const GraphQLRootQuery = new GraphQLObjectType({
  name: 'Root Query',
  fields: (() => ({
    user: {
      type: GraphQLUser,
      args: {
        id: {
          type: new GraphQLNonNull(GraphQLID),
        },
      },
      resolve: ((root, args) => users[args.id]),
    },
    viewer: {
      type: GraphQLUser,
      resolve: (() => users[mockViewerID]),
    },
    ...
  })),
});
const UpdateUserEmailMutation = {
  type: GraphQLUser,
  args: {
    userId: {
      type: new GraphQLNonNull(GraphQLID)
    },
    newEmail: {
      type: new GraphQLNonNull(GraphQLString),
    },
  },
  resolve: (root, args) => {
    users[args.userId] = args.newEmail;
    return users[args.userId];
  }
};
const GraphQLRootMutation = new GraphQLObjectType({
  name: 'Root Mutation',
  fields: (() => ({
    updateUserEmail: UpdateUserEmailMutation,
    ...
  })),
});
const schema = new GraphQLSchema({
  query: GraphQLRootQuery,
  mutation: GraphQLRootMutation,
});
export default schema;

To expose the schema to the client, we create a route using express-graphql:

import express from "express";
import graphQLHTTP from "express-graphql";

import MyGraphQLSchema from "./schema";

const app = express();

app.use(
  "/graphql",
  graphqlHTTP({
    schema: MyGraphQLSchema,
  })
);

If you are not using Express, there are alternatives for other web frameworks. GraphQL also isn’t limited to JavaScript, as a query language, it can be (and is) supported by many different languages.

To explain why I think GraphQL is the future, and why I chose it I’ll show some of the problems with REST, and how GraphQL solves them:

Overfetching

Suppose I have a list of users, and I want to query each one’s email. With RESTful API I would probably have the following route:

GET /api/users/:id

If I’ll send this GET message, I would probably get a JSON object which looks somewhat like this:

{
  "id": 1,
  "email": "user1@mail.com",
  "username": "user1",
  ...
}

As you may have noticed, needing only the email of each user, the server is forced to send me much more data. This can be slow, and lead to a bad user experience, especially for mobile applications, where bandwidth can be slow.

With GraphQL, this problem no longer exists. Using GraphQL, we declare objects (or types), with specified typed fields. When the client needs some data, it sends a JSON-like string that expresses only the field it requires.

Using the example GraphQL schema I’ve shown, all the client needs to send is:

query GetUserEmail {
  user(id: 1) {
    email
  }
}

And he will get the exact information he needs. This is true also for sub-fields.

query GetUserFriends {
  user (id: 1) {
    friends: {
      id,
      email
    }
  }
}
Underfetching

Continuing with the previous example, in a scenario where the client needs the email of all the friends of user #1, this is what the client will need to send to a RESTful API endpoint:

GET /api/users/1 // will return object with field friends: [2, 4, 5, 7]
GET /api/users/2
GET /api/users/4
GET /api/users/5
GET /api/users/7

I think the problem is very clear. Requiring a fairly simple piece of data, the client is required to send many requests. This, again, can be very slow.

We already saw how GraphQL solves it, the last GraphQL query we saw is sufficient for our needs, and will result in something which looks like this:

\POST /graphql
[
  {
    "id": 2,
    "email": "user2@email.com"
  },
  {
    "id": 4,
    "email": "user4@email.com"
  },
  {
    "id": 5,
    "email": "user5@email.com"
  },
  {
    "id": 7,
    "email": "user7@email.com"
  }
]

These are only the main reasons one would choose GraphQL over REST, there are more, which you can read in almost any article comparing the two. However, these are not the reasons I chose to use GraphQL. A project like Question It can do just fine with those REST “problems”.

I chose to use GraphQL in order to learn GraphQL. I also chose it because, along with Relay, this seems to be the most appropriate way to implement data fetching. And now, I urge you to choose it, since I sincerely believe, this is the future. REST isn’t going away any time soon, and GraphQL is still young and evolving, but sooner or later I believe, it will be as popular as REST is now.

We're still left with the question? What are we doing with all that data, and how can we manage it efficiently?

React + Relay + GraphQL is the future
Relay - the bridge between your react client and your server data

Relay

The most popular way of managing data amongst React developers is to use redux and react-redux. I didn’t use Redux in my application, but I’ll talk about it because I think it’s a tool every JavaScript developer should at least know exists.

Redux and Relay are two implementations of Facebook’s Flux pattern. Flux is a pattern that dictates how data should be managed in web applications, specifically ones created with React. In Flux, actions are sent to a dispatcher, which dispatches them to all of the stores. Stores contain the state of a set of connected components, which updates their state (and the view), according to dispatched actions.

While Redux and React are fundamentally different implementations of Flux, they are similar in a way. Both of them utilize a single, global store, instead of a set of stores.

So, let me explain each one in more detail.

Redux

Redux is a state management library for JavaScript created by Dan Abramov. While being a sort of Flux implementation, it is by no way exclusive to React applications and can be used to manage state in all sorts of JavaScript applications.

In Redux, the application’s state can be anything: JSON object, string, array, or even simple integer. The application’s state is read-only and can only be changed by pure functions called reducers. Pure functions are functions with no side effects, they don’t change or delete existing data but can return a changed copy. Reducers are pure functions that accept a state and an action and return a new state, without changing the given state or anything else. Redux actions JavaScript object containing the type property. They can also contain additional properties, according to your application’s needs.

As you see, Redux has nothing to do with React, really. But since React already translates state to view, it is only natural to let it translate the Redux’ state. That’s what react-redux is for. Using Redux, React components can be divided into Presentational Components, ones that are responsible for rendering the view from the props, and Container Components, ones that read the state and dispatch actions to change it.

Container components are created using the Redux connect function, which creates a container of representational component, which passes state-reading props via mapStateWithProps, and state-changing props via mapDispatchWithProps.

One more thing to notice is Redux has nothing to do with data fetching, it is used to manage the data, no matter where it comes from. Usually, the data comes from the server, which is why Redux is often used with a library like redux-thunk which extends Redux to support asynchronous actions, ones that used fetch or XMLHttpRequest, for example.

React + Relay + GraphQL
Relay binds React components and GraphQL endpoints

Relay is a GraphQL client and a state manager created by Facebook for React. It basically binds between React components (the view), and GraphQL endpoints (the data).

In Relay, the state is merely a projection of the data in your GraphQL endpoint, required by your application. Relay defines the entry points of your GraphQL endpoint, that should be accessible, using our GraphQL example, a route that exposes a root query for user #1 will look like this:

export default class extends Relay.Route {
  static queries = {
    game: () => Relay.QL`query { viewer }`,
  };
  static routeName = "MyRoute";
}

Relay exposes the GraphQL entry points (Relay routes) to React component, by creating Containers. Containers connect the route and the React component using GraphQL fragments, which are the required subset of fields of the exposed GraphQL root field, creating a container is fairly simple:

export default Relay.createContainer(MyComponent, {
  fragments: {
    viewer: () => Really.QL`
      fragment on User {
        username,
        friends {
          username
        }
      }
    `,
  },
});

The root viewer field became available thanks to our route, which then was used by our container to query the required subset of our viewer fields. Doing this, you define only the data required by your component. Linking the routes to the components is done by rendering a root container: a container for our root component:

ReactDOM.render(
  <Relay.RootContainer
    Component={MyComponentContainer}
    route={new MyRoute()}
  />,
  document.getElementById("root")
);

Relay does more than offer an API for GraphQL queries. It also manages the data of your app, caching the data and saving you unnecessary server requests. Assuming we have two or more components in our app, which require some mutual data. Relay will query the data for the first component, and then will serve the mutual data for both components, and query for any missing data. When a mutation is committed by any component, Relay will automatically update the affected components, and keep an updated view.

Relay has another cool feature: It allows you to configure optimistic updates for your mutations. You can define the optimistic result of each mutation, which allows your app to keep its’ natural flow, when the real result will come, Relay will notify the components so they can update accordingly. Of course, you can distinguish optimistic updates from real ones, so you can set the view accordingly.

Another useful feature is the creation of connections between your GraphQL objects. Previously, we defined the friends of a user as a list of users. This makes sense, but Relay offers more. In Relay, every GraphQL object can be a node. nodes can be connected by connections and edges between the nodes (as in real graphs). The edges themselves can contain more useful information, and the same thing goes for the connection itself.

const GraphQLUser = new GraphQLObjectType({
  ...
  friends: {
    type: friendConnection,
    args: connectionArgs,
    resolve: (user, args) => connectionFromArray(user.friends, args)
  }
  ...
});
const {connectionType: friendConnection} =
  connectionDefinitions({name: 'Friend', nodeType: GraphQLUser});

Here I tweaked the schema, so a user will contain a connection to its’ friends. The connection type can be extended even further, to contain connection fields (number of friends, for example), and to contain edges fields (friendship date).

Connections are also accompanied by connectionArgs. connectionArgs are a set of arguments that are used for pagination: first, last, before & after, and they allow you to fetch edges one “page” at a time, and according to your needs. The only limitation for creating connections is that both sides of the connection must implement the nodeInterface, I won’t elaborate on nodeInterface. In short, it’s a GraphQL object interface used to support Relay’s refetching.

If you’re using react-router in your application, you might find react-router-relay useful. react-router-relay integrates your routes with your Relay containers, and allows you to use route and query parameters as your Relay variables. Setting up react-router-relay is pretty simple, so I’ll just refer you to the documentation on the GitHub repo.

Redux vs Relay
Why choose one over the other?

The same thing I said about GraphQL is true here. I didn’t use Relay because it is better, nor because it was a much-needed requirement. I chose Relay to learn. Relay is even younger than GraphQL, but it is the future of React development. Both Relay and GraphQL, have great plans ahead: real-time updates (using web sockets), defer (prioritizing data, the distinction between primary and secondary data), stream (fetching a huge amount of data, which will result in a stream of non-blocking responses, one object at a time), and more…

Still, when to use Redux or Relay?

For small to medium applications, Redux would probably be the way to go. It is simple and lite, easy to learn and understand. For big, enterprise applications, Relay would probably be the optimal choice, for all the optimizations and features it offers. Still, Redux can perform absolutely well in large applications, as well as Relay in small ones. Some applications, maybe wouldn’t need either, and in the end, it comes to what the developer feels most comfortable with.

Well, that’s it. I hope you liked this (quite long) article, stay tuned :)

Previous: How I’ve Built My First React Application II
Next: How I’ve Built My First React Application IV