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.
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.
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.
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
.
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.
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.
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
.
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
.
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.
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.
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 Feed
s, 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.
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
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.
Notice the chain of edge
declarations.(edges
are created with connection
)
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.
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.
The server receives that query as a POST /graphql
And returns a large object (data graph) that was built based on information contained in
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.
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.
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.
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.