Self Education Self Education

ToEatApp Rails-React-Relay-GraphQL Tutorial - Queries

If you are curious about how some of these tools work together, you might look at the application setup post.

Background & Context

Facebook has published Relay and GraphQl. They have done this to make some bold moves away from imperative REST base APIs to declarative relay/graphql based APIs. In a REST mindset, developers have become accustomed to dealing with http methods and controller endpoints that frankly do all kinds of things.

Facebook’s outlook is that instead of having many endpoints that might do things in an unclear fashion, it makes more sense to declare precise logic related to events that developers want to occur and then route all that traffic through one server endpoint. GraphQl represents the declarations around what data is accesible and how it can be accessed and Relay represents a tool that allows clients to communicate with servers.

Typically, web developers expect http GET to return information, POST to create information, PUT/PATCH to update information and DELETE to destroy information. Graphql cares only about reading from a data store, as expressed through Queries, Types and Fragments, and writing to a data store, as expressed through Mutations.

Consider toeatapp.startuplandia.io, we want to know what food trucks exist and what orders they currently possess. In a REST based world, a developer might make a query that looked like GET /api/food_trucks which returns some kind of Array like collection [FoodTruck1, FoodTruck2, FoodTruckN] with each FoodTruck possibly having some kind of child relationship like FoodTruck.orders where .orders points to an Array like collection, [Order1, Order2, OrderN]. Without getting ultra meta and attempting to explain how the process works, we are just going to dive in and navigate the process required to render FoodTrucks and Orders.

Notice the Setup

Notice on the server, we will be using graphql-relay to implement graphql. On the client we will be using a bunch of different node packages, look at package.json for more details.

# Gemfile
gem 'graphql-relay'

Also notice on the server, instead of a typical REST styled API, we will be routing ALL database read/write requests through the same server endpoint.

# routes.rb
Rails.application.routes.draw do
  root to: "trucks#home"
  post "/graphql" => "trucks#graphql"
end

Notice that the TrucksController responds to #graphql which has the sole function of receiving a query and returning whatever the query happens to return to the client. If you find yourself having an immediate, viscerally negative reaction to this thought, you are not alone. This is part of the code that represents a binary move away from REST ideology.

class TrucksController < ApplicationController
  def graphql
    query = GraphqlSchema.execute(params[:query], variables: params[:variables])
    render json: query
  end
end

schema.json

Anytime you change code on the server that tells or changes any of the structure in how Graphql is organizing data for Relay, you must update schema.json (bin/rake generate_graphql_schema) and restart the webpack process.

Rendering The React App

Literally, the React app gets loaded into the dom via this snippet from app.jsx.

// app.jsx
ReactDOM.render(<ToEatApp />, document.getElementById('to-eat-app'))

Once the app gets rendered, the RelayRouter takes control of the process and will attempt to render whatever view component is associated with the current route, in the case of the application root /, the RelayRouter has been programmed to load the ToEatIndex component.

// router.jsx
import ToEatIndex from './components/food_trucks/index.jsx'

const UserFleetQueries = {
  user_fleet: () => Relay.QL`query { user_fleet }`
}

export default function ToEatApp() {
  return (
    <RelayRouter history={browserHistory} forceFetch={true}>
      <Route path="/" queries={UserFleetQueries} component={ToEatIndex} />
    </RelayRouter>
  )
}

Notice that the router renders a queries property that is rendering UserFleetQueries, which is a constant that points to a GraphQl fragment.user_fleet is the client-side reference to the GraphQL application declared type UserFleetType and represents the root access point of the data hierarchy that will allow us to ulimately arrive at FoodTruck.items. Notice that the type definition has a connection named :fleets which resolves to an Array containing Fleet.first. It is extremely important to notice that the connection is called a FleetType.connection_type as FleetType is another graphql type definition.

# user_fleet_type.rb
UserFleetType = GraphQL::ObjectType.define do
  name "UserFleet"
  description "container for user fleets"

  connection :fleets, FleetType.connection_type do
    resolve -> (obj, args, context) {[Fleet.first]}
  end
end

We will now trace the linkage that starts with our root UserFleetType and begins an association linking Fleet.first to UserFleet via fleets as declared in FleetType. Notice that FleetType declares :id and :name as fields, a global_id_field and another connection named food_trucks that links food_trucks to obj where obj is a pointer to the parent object defined by UserFleetType that of Fleet.first.

# fleet_type.rb
FleetType = GraphQL::ObjectType.define do
  name "Fleet"
  description "food truck who?"

  interfaces [NodeInterface.interface]
  global_id_field :id

  field :id, !types.ID
  field :name, !types.String
  connection :food_trucks, FoodTruckType.connection_type do
    resolve -> (obj, args, context) { obj.food_trucks }
  end
end

Notice in the above FleetType definition, we have written interfaces [NodeInterface.interface] which is Graphql speak for ‘hey, this type definition has records in the db that someone on the client might want to look at so make sure to include them in the return blob of the query’. NodeInterface is defined in node_interface.rb

Let’s keep moving through the rabbit hole by looking at the FoodTruckType definition. Notice that FoodTruckType has the fields id, name, home_town and fleet_id and the connection :orders of type OrderType which resolves to the obj.orders where obj is a pointer to the parent FleetType.

FoodTruckType = GraphQL::ObjectType.define do
  name "FoodTruck"
  description "food truck"

  interfaces [NodeInterface.interface]
  global_id_field :id

  field :id, !types.ID
  field :name, !types.String
  field :home_town, !types.String
  field :fleet_id, !types.ID

  connection :orders, OrderType.connection_type do
    resolve -> (obj, args, context) { obj.orders }
  end
end

To finish the chain of relationships, let’s look at the OrderType definition, notice we have written interfaces [NodeInterface.interface] which gives our OrderType connection actual nodes in our soon to be sent to the client data blob, also notice that this definition doesn’t have any defined connections because there is no other information to be related or connected.

# order_type.rb
OrderType = GraphQL::ObjectType.define do
  name "Order"
  description "tasty foods"

  interfaces [NodeInterface.interface]
  global_id_field :id

  field :id, !types.ID
  field :tasty_item, !types.String
  field :food_truck_id, !types.ID
end

The connection between ActiveRecord and GraphQL::ObjectType.

You might find yourself wondering how the GraphQL::ObjectType definitions relate to active record objects. The goal of GraphQL is to define relationships between information. Once the relationships are in place, then Relay can shine by understanding how to make the smallest queries to the server and update only the parts of the data ‘graph’ that are necessary. Ultimately, GrapQL doesn’t care what actual data lives underneath the chain of structured relationship but we do, so we’ve told GraphQL what data to care about.

The connection chain.

# user_fleet_type.rb
UserFleetType = GraphQL::ObjectType.define do
  connection :fleets, FleetType.connection_type do
    resolve -> (obj, args, context) {[Fleet.first]}
  end
end

# fleet_type.rb
FleetType = GraphQL::ObjectType.define do
  connection :food_trucks, FoodTruckType.connection_type do
    resolve -> (obj, args, context) { obj.food_trucks }
  end
end

# food_truck_type.rb
FoodTruckType = GraphQL::ObjectType.define do
  connection :orders, OrderType.connection_type do
    resolve -> (obj, args, context) { obj.orders }
  end
end

# order_type.rb
OrderType = GraphQL::ObjectType.define do
  # OrderType doesn't have any connections
end

# UserFleetType.fleets => [Fleet.first]
# FleetType.food_trucks => Fleet.first.food_trucks => [ FoodTruck1, FoodTruck2 ]
# FoodTruckType.orders => FoodTruckN.orders => [ Order1, Order2 ]

RootQueryType

GraphQL and Relay need a data graph to have a root object. Consider Facebook’s general use case a User has one or many Feeds, in this case the authenticated User.id would be the root of the graph. In our example case, we have ignored the process of authenticating a user and have built an arbitraty root object.

# root_query_type.rb
RootQueryType = GraphQL::ObjectType.define do
  name "RootQuery"
  description "root query"

  field :node, field: NodeInterface.field

  field :user_fleet do
    type UserFleetType
    # arbitrary root object OpenStruc.new(id: 1)
    resolve -> (obj, args, context) { OpenStruct.new(id: 1) }
  end
end

Notice in the above ObjectType definition we have defined a RootQueryType and given it a field user_fleet. This user_fleet brings us back to the beginning of this post, to the moment where Relay is querying to the server for UserFleetQueries

// router.jsx
const UserFleetQueries = {
  user_fleet: () => Relay.QL`query { user_fleet }`
}

Now, consider everything you’ve read above, it might not make a ton of sense, which is why I’m writing this tutorial. Look at the below screenshot of the actual data graph being displayed on toeatapp.startuplandia.io. Notice the root of the data graph, as expressed by Object {__dataID__: "client:10658033131", fleets: Object} see if you can trace the chain of relationships from user_fleet down to orders.

Edges and Nodes

Do you find yourself looking at the above screenshot and wondering about the role of edges and node? If you are new to graph ideology, which I was before I began working with these tools, I didn’t understand the copious referencing to nodes and edges in Facebook’s documentation. Consider the below jpg, in a graph a node is literally anything Thingy, Object or Node and an edge is a reference to the connection between two different nodes.

You might be now saying to yourself, ‘yes I understand this reasonably basic concept but how did all the references to nodes and edges appear in our data graph?’. GraphQL has built our client side data structure for us and based on our ObjectType definitions, we have declared places where nodes or edges should exist.

Notice the chain of node declarations.

# root_query_type.rb
RootQueryType = GraphQL::ObjectType.define do
  field :node, field: NodeInterface.field
end

# user_fleet_type.rb
UserFleetType = GraphQL::ObjectType.define do
  # notice UserFleetType doesn't have any node information
end

# fleet_type.rb
FleetType = GraphQL::ObjectType.define do
  interfaces [NodeInterface.interface]
end

# food_truck_type.rb
FoodTruckType = GraphQL::ObjectType.define do
  interfaces [NodeInterface.interface]
end

# order_type.rb
OrderType = GraphQL::ObjectType.define do
  interfaces [NodeInterface.interface]
end

Notice the chain of edge declarations.(edges are created with connection)

# root_query_type.rb
# the root query isn't a connection so much as it is the base
# from where connections can be connected to
RootQueryType = GraphQL::ObjectType.define do
  field :user_fleet do
    type UserFleetType
    resolve -> (obj, args, context) { OpenStruct.new(id: 1) }
  end
end

# user_fleet_type.rb
UserFleetType = GraphQL::ObjectType.define do
  connection :fleets, FleetType.connection_type do
    resolve -> (obj, args, context) {[Fleet.first]}
  end
end

# fleet_type.rb
FleetType = GraphQL::ObjectType.define do
  connection :food_trucks, FoodTruckType.connection_type do
    resolve -> (obj, args, context) { obj.food_trucks }
  end
end

# food_truck_type.rb
FoodTruckType = GraphQL::ObjectType.define do
  connection :orders, OrderType.connection_type do
    resolve -> (obj, args, context) { obj.orders }
  end
end

# order_type.rb
OrderType = GraphQL::ObjectType.define do
  # OrderType doesn't have any edges
end

You might at this point have begun to notice that our data graph is litered with the key __dataID__. __dataID__ is the unique key that GraphQL/Relay use to manage information inside the data graph. These unique id fields create a way for Relay/GraphQL to know when, where and how to query/mutate the smallest parts of the data graph possible. In a typical active record world, a developer might find themselves refreshing an entire collection of records to update the DOM by literally querying to the server for all records in a collection. In the GraphQL/Relay world, Relay manages the process of knowing the smallest query/mutations to execute and these __dataID__ fields are essential to that process.

Notice that in our ObjectType definitions, we tell GraphQL to use specific id’s in our records to pay attention to, which creates the linkage between the __dataID__ and the reacord’s actual id in the database.

# root_query_type.rb
RootQueryType = GraphQL::ObjectType.define do
  # root query isn't a record so no ids to keep track of
end

# user_fleet_type.rb
UserFleetType = GraphQL::ObjectType.define do
  # user fleet type is an abstract container object 
  # so no ids to keep track of
end

# fleet_type.rb
FleetType = GraphQL::ObjectType.define do
  global_id_field :id
end

# food_truck_type.rb
FoodTruckType = GraphQL::ObjectType.define do
  global_id_field :id
end

# order_type.rb
OrderType = GraphQL::ObjectType.define do
  global_id_field :id
end

This entire pattern might seem messy and unclear at first but spend some time with it and it will begin to make sense. It took me three very serious weeks to understand how all these patterns worked together and my goal with these blog posts is to reduce that time to hours for other developers.

Let’s Recap

In order to render the entire data graph encompassing the chain of relationships between UserFleet and Orders, when the dom is loaded, our React application uses the RealyRouter to query to the server for the user_fleet query.

const UserFleetQueries = {
  user_fleet: () => Relay.QL`query { user_fleet }`
}

export default function ToEatApp() {
  return (
    <RelayRouter history={browserHistory} forceFetch={true}>
      <Route path="/" queries={UserFleetQueries} component={ToEatIndex} />
    </RelayRouter>
  )
}

The server receives that query as a POST /graphql

 Prefix Verb URI Pattern        Controller#Action
   root GET  /                  trucks#home
   graphql POST /graphql(.:format) trucks#graphql

And returns a large object (data graph) that was built based on information contained in

# root_query_type.rb
RootQueryType = GraphQL::ObjectType.define do
  # ...
end

# user_fleet_type.rb
UserFleetType = GraphQL::ObjectType.define do
  # ...
end

# fleet_type.rb
FleetType = GraphQL::ObjectType.define do
  # ...
end

# food_truck_type.rb
FoodTruckType = GraphQL::ObjectType.define do
  # ...
end

# order_type.rb
OrderType = GraphQL::ObjectType.define do
  # ...
end

Which becomes available to our React application as a standard javascript Object/Array blob sort of thing that can be interacted with inside the ToEatIndex component defined by import ToEatIndex from './components/food_trucks/index.jsx' in router.jsx.

Very Important Detail Relay and React require a Container to be defined in order to communicate to the component about the contents of the query being provided. This is a critical step and cannot be ignored.

// index.jsx
var Index = React.createClass({
  getInitialState() {
    return ({  
      foodTrucks: [],
      fleetId: null
    })
  },

  componentDidMount() {
    console.log(this.props.user_fleet)
    this.setState({fleetId: this.props.user_fleet.fleets.edges[0].node.id})
    this.updateTruckInfo(this.props)
  },
})

Schema.json

Let’s not forget about the crucial step of generating schema.json. Everytime a change is made to any of the Ruby GraphQL type definitions or mutations, a new schema.json file must be regenerated by running bin/rake generate_graphql_schema. Notice that package.json tells graphql to look for schema.json as the schema file. Everytime a new schema.json file is built, the Webpack process needs to be restarted.

"metadata": {
  "graphql": {
    "schema": "./schema.json"
  }
}

Containers

Notice the bottom of index.jsx. A Container tells the React component the minimum information that must be provided in order for the component to be able to render itself. Notice how every piece of information that we want the DOM our React component to have access to is declared below.

export default Relay.createContainer(Index, {
  fragments: {
    user_fleet: () => Relay.QL`
      fragment on UserFleet {
        fleets(first: 10) {
          edges {
            node {
              id
              name
              food_trucks(first: 10) {
                edges {
                  node {
                    id
                    name
                    home_town
                    fleet_id
                    orders(first: 10) {
                      edges {
                        node {
                          id
                          tasty_item
                          food_truck_id
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    `
  }
})

In Closing

This is a particularly long and complicated tutorial. You should congratulate youself for reading this far and if everything didn’t sink in on first pass, that is really ok, the ideas are a bit messy to think about and slightly more messy to communicate about. You might reread the post, look at the source code and always feel free to reach out to me with questions, [email protected].

Next Steps

Read the next post in this series, ToEatApp React-Relay-GraphQL Tutorial - Mutations.

Talk

A Dream Team

We have traveled many miles.

We will help you build, scale, grow.