Part 4: Rails ❤️ GraphQL Crash Course (Example 1)

Welcome back to Rails ❤️ React.

In Part 1 of this mini course to bridge Rails and React, I introduced you to the concepts of monorepos and conjoined apps vs. separated Rails-React apps.

In Part 2, I went over how to think about building separated apps (I do not recommend it because of the extra work involved in maintenance, testing, and deployment) and in Part 3 I went over how to set up a React frontend on top of a Rails backend (a monorepo with conjoined app, a single test suite, and a single Rails-based deployment).

Now, we’re going to talk about a very important piece of tech that will operate between the two (really inside the Rails app): GraphQL. Let’s dive right in with GraphQL. This applies to you for both the monorepo (Part 3) paradigm and the separated apps paradigm (Part 2). Of course, if you have separated apps today we’ll be focusing on setting up GraphQL for the Rails part of your stack.

Important: Make sure your app name is perfectly TitleCase. If you have parts of your TitleCase app name that have capital letters, the GraphQL installer will run into issues. For example, don’t make an app called GraphQLExample. You’ll notice that the “QL” is capitalized, and this won’t work with the generators. In this example, I will start with :

rails new HelloWorldGraphQl

Notice that the “Ql” has a lowercase ‘l’ as the second letter.

1. Add to your Gemfile

gem 'graphiql-rails', group: :development
gem 'graphql'

The GraphiQL is the development toolkit for examining the GraphQL endpoint. If you want this to be available outside your development environment, don’t add it to the development group only here.

Now after you run bundle install, you’ll run the GraphQL setup

2. Run the Graphql Installer

 rails generate graphql:install

Let’s take a look at what’s happened.

Don’t do this!!!!

      create  app/graphql/types
      create  app/graphql/types/.keep
      create  app/graphql/hello_world_graph_ql_schema.rb
      create  app/graphql/types/base_object.rb
      create  app/graphql/types/base_argument.rb
      create  app/graphql/types/base_field.rb
      create  app/graphql/types/base_enum.rb
      create  app/graphql/types/base_input_object.rb
      create  app/graphql/types/base_interface.rb
      create  app/graphql/types/base_scalar.rb
      create  app/graphql/types/base_union.rb
      create  app/graphql/types/query_type.rb
add_root_type  query
      create  app/graphql/mutations
      create  app/graphql/mutations/.keep
      create  app/graphql/mutations/base_mutation.rb
      create  app/graphql/types/mutation_type.rb
add_root_type  mutation
      create  app/controllers/graphql_controller.rb
       route  post "/graphql", to: "graphql#execute"
     gemfile  graphiql-rails
       route  graphiql-rails
      create  app/graphql/types/node_type.rb
      insert  app/graphql/types/query_type.rb
      create  app/graphql/types/base_connection.rb
      create  app/graphql/types/base_edge.rb
      insert  app/graphql/types/base_object.rb
      insert  app/graphql/types/base_object.rb
      insert  app/graphql/types/base_union.rb
      insert  app/graphql/types/base_union.rb
      insert  app/graphql/types/base_interface.rb
      insert  app/graphql/types/base_interface.rb
      insert  app/graphql/hello_world_graph_ql_schema.rb
Gemfile has been modified, make sure you `bundle install`

Notice that this has been added to your config/routes.rb file

 if Rails.env.development?
    mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
  end
  post "/graphql", to: "graphql#execute"

This will be our only entrypoints into GraphQL:

  1. The /graphiql web interface (For debugging and development).
  2. The /graphql endpoint which will serve as the single gateway for all queries.

Get Comfy With The Graph

GraphQL is a querying standard. It’s like SQL in that it can read, modify, or return information about the data and data schema. It’s more like JSON or XML in that it was designed to be an internet-first standard, so it is engineered around two computer systems talking to each other over the internet. (In contrast, a SQL query happens directly between the server and database.)

What is GraphQL?

Rail-Hosted Graph QL

Unlike the Apollo-provided service, hosting our own GraphQL in our Rails app means we basically bolt on a GraphQL endpoint onto our existing Rails app, then define several schemas, then define how those schema work with our existing ActiveRecrod objects, and that’s: you have a GraphQL-supporting API.

This lesson will serve as a crash course in GraphQL from the server’s perspective. That is, we are concerned here as we were a backend developer writing this GraphQL endpoint for consumption by others.

Your Schema

The first thing you need to know about your Rails GraphQL schema, which was created by the installer for you, lives at app/types/YourAppName_schema.rb

It includes two key directives at the top: mutation and query.

class YourAppNameSchema < GraphQL::Schema
  mutation(Types::MutationType)
  query(Types::QueryType)

These directives tell your GraphQL schema definition — which, remember, is a queryable interface— to include both mutations and queries we define.

A Type is like a container— the metaphor I use is a sport bottle, like the Helios Sport bottle which you can get at the Helios Merch Shop:

The Helios Sport Bottle available exclusively at the Helios Merch Shop makes an excellent metaphor to be used when explaining Type definitions in GraphQL.

The type is a definiton of the shape of the kind of liquid we’ll take on our hike today. The hike represents both the preparation we make to go on the journey (these are the type definitions — or sport bottles we’ll take on our hike), and also the concept of drinking water, Gatorade, or other thirst-quenching liquid (that will be our data).

The hike we are taking in the California wilderness is the GraphQL query — in metaphor — so our Query is the idea of seeking a rejuvenating experience. That is, we sought (queried for) a rejuvenating experience and the wilderness returned the rejuvenating experience to us.

Along the way, we took the Helios sport bottle like the one above (Remember, the sport bottle is the Type definition of what we can plan for, carry-in, and carry-out. It is a metaphor that represents the shape of data we take in and then consume along the hike).

The Mutation what we affect when we go hiking. In the beautiful California where we can take only pictures and leave only footprints, we do not make mutations.

When we go hiking, we carry our GraphQL type definitions (sport bottles) with us on our GraphQL queries (journeys), but we make no GraphQL mutations (leave no footprints) on the land. This means we will have a low environmental impact when enjoying its natural splendor.
When we go hiking, we carry our GraphQL type definitions (sport bottles) with us on our GraphQL queries (journeys), but we make no GraphQL mutations (leave no footprints) on the land. This means we will have a low environmental impact when enjoying its natural splendor.

Mutations are what happen when we want our Query — our journey with Type definitions (the sport bottle), the data, — to make a change to the world.

For example, if we take our Sport bottle to, say, a business meeting instead of the wonderful California wilderness, it might look like this (below).

A business meeting in which we are making a figurative GraphQL mutation (asking for a change).
A business meeting in which we are making a figurative GraphQL mutation (asking for a change).

In this meeting, we are asking for someone to change — to mutate — something about the world. We still packed our sport bottle (Type definitions), we still made the trip to have the meeting (Query), but the purpose of our trip is to make a change to the world instead of just taking pictures as we were doing when we were hiking in California.

Now that we have our mental models mapped, we can begin to implement our objects. Remember, these are just Ruby classes

Now open up app/graphql/quer

Graphiql

The second thing to know about learning GraphQL is that it has a built-in web interface playground. Here you can both learn about how GraphQL work generally but also introspect any given

The tool is called graphiql and it is part of the standard installation. However, unless you want to expose your API to the world, it is typically disabled for production.

Don’t use Single Quotation Marks

In many languages single quotation marks ' and double quotation marks " are interchangeable. In GraphQL, your query must use double quotation marks. You will get parse errors if you fail to use double quote marks.

The Most Basic Introspection

query {
   __schema {
     types {
       name
       description
     }
   }
 }

Let’s try this in our browser to see if it works:

Ok, that’s the very basic setup for GraphQL on your app. Now you must think about how to define your data model, GraphQL Types, Mutations, and Queries.

3. Set Up your Objects in Rails

Let’s generate some model objects:

rails generate User name:string email:string

rails generate Post user_id:integer title:string blurb:text

You guessed it! Be sure to add these active record relationships:

class Post < ApplicationRecord
  belongs_to :user
end

class User < ApplicationRecord
  has_many :posts
end

4. Generate GraphQL types for the Objects

Now, use the GraphQL generators to generate Types based on your existing model objects

rails generate graphql:object User

rails generate graphql:object Post

This generates a two files: app/graphql/types/user_type.rb and app/graphql/types/post_type.rb

Notice that the installer has read your field names for you from your database object. This was generated automatically:

module Types
class PostType < Types::BaseObject
field :id, ID, null: false
field :user_id, Integer, null: true
field :title, String, null: true
field :blurb, String, null: true
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
end
end

Check Your Work!

Open /graphiql and run

query {
   __schema {
     types {
       name
       description
     }
   }
 }

Make sure you do not see a crash.

In Rails, when Your Type Definitions are Wrong, the Graphiql interface Crashes, but not with the Real Error Message

5. Modify Your Type Definitions With Their Own Attributes

Add this code to your user_type.rb file:

field :posts_count, Integer, null: false

def posts_count
object.posts.count
end

Here, we are defined a posts_count attribute of the user type that will simply return the count of related posts for that user object.

Finally, we need to wire up our queries themselves:

Open app/graphql/types/query_type.rb and add the code in orange. Note that this file has an example comment “# TODO: remove me” which you should, of course, remove, as well as the “test_” attributes that come by default:

module Types
  class QueryType < Types::BaseObject
    # Add `node(id: ID!) and `nodes(ids: [ID!]!)`
    include GraphQL::Types::Relay::HasNodeField
    include GraphQL::Types::Relay::HasNodesField

    # Add root-level fields here.
    # They will be entry points for queries on your schema.

    field :users, [Types::UserType], null: false

    def users
      User.all
    end

    field :user,  Types::UserType, null: false do
      argument :id, ID, required: true
    end
    
    def user(id:)
      User.find(id)
    end
  end
end

Now let’s see if we can query

5. Query

A query is a question. Questions change the world.

First lets query just for all users:

query {
   users{
     name
     email
  }
 }

Does that work?

Now, notice that we added a posts_count to the Type definition.

To get this from our query, we simply pass it as an additional attribute that we want returned:

query {
  users{
    name
    email
    postsCount
  }
}

Let’s ask our GraphQL implementation about the users. You will notice that this example is disappointingly REST-like, something GraphQL developer don’t like to mention, because it harkens back to the dark ages of web development when web developers used differing endpoints to load different parts of data, sometimes building complicated side-loading facilities to avoid multiple querying, and all the time locking themselves into strict API schematics that made it slow to change. (And required APIs to be versioned).

In Example #2, I’ll cover a design that doesn’t look so REST-like. But for bridging the two worlds of Rails and React and providing parity to Rails mental models, the above example works well.

Example App