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.
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:
- The /graphiql web interface (For debugging and development).
- 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?
Rails-Hosted GraphQL
Unlike the Apollo-provided service, hosting your own GraphQL in our Rails app means you can basically bolt on a GraphQL endpoint onto our existing Rails app, then define several type definitions, queries, and mutations. The types, queries and mutations tie into our ActiveRecrod objects, and Bob’s your uncle: you have a GraphQL-supporting API backed by Rails.
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/graphql/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 definition 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 is what we affect when we make the trip (the Query). In the beautiful California where we can take only pictures and leave only footprints, we do not make mutations.
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).
In this meeting, we are asking for someone to change — to mutate — something about the world. We still packed our sport bottle (Type definition), we still made the trip to have the meeting (Query), but the purpose of our trip is to ask our colleagues for a specific change (Mutation).
Now that we have our mental models mapped, we can begin to implement our objects. Remember, these are just Ruby classes.
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 GraphQL schema definition.
The tool is called Graphiql (notice the i!) 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. Sometimes, however, you want to enable your GraphQL endpoint to the world— for example, you have an open API or you want to encourage others to learn about and use your GraphQL endpoint.
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.
The best way to debug this is to simply boot your app and make sure your Type definitions themselves can load correctly.
5. Modify Your Type Definitions With Their Own Attributes
A type definition exclaims to the world what shape the data will take.
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
}
}
Go ahead and ask the GraphQL implementation for information about the users.
You will notice that this example is disappointingly REST-like, something GraphQL developers 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. In these sad times, sometimes devs even built 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).
GraphQL does away with this nonsense and frees the frontend developer to do whatever they want, whenever they want.