I was perusing the Ruby on Rails courses on Udemy recently and came across a course on using GraphQL with Ruby on Rails. It was a short hour, and a half of video content, so I purchased it and followed along with the videos. The course instructor, Alex Deva, does an awesome job explaining the concepts and walking through small projects that demonstrate how and when to use GraphQL. I had heard of GraphQL before, but I didn’t have any exposure to it up until this course.
GraphQL is an alternative to using a REST API where you have two pieces of software (like a backend, and a frontend) that need to communicate with each other. In a lot of cases, the underlying data for the application needs to change based on things that happen, for instance when the user takes some sort of action like clicking a button. With GraphQL you create queries to describe the exact data you want to get back. The idea with GraphQL is that you get back exactly what you need instead of having to deal with additional data that isn’t necessary, which you often do with REST.
The course recommends installing GraphiQL, and I really enjoyed working with it. GraphiQL was pretty easy to install, and it’s very simple and lightweight. You have to add gem "graphql"
to your Gemfile and start the Rails server, then you can start interacting with GraphiQL on port 3000.
With GraphQL you read data through the use of queries. By default, the project creates a file called query_type.rb
where you can define root level fields which can be added as a data point to retrieve in the queries. The example test field it provides looks like this:
field :test_field, String, null: false,
description: "An example field added by the generator" do
argument :name, String, required: true
end
def test_field(name:)
Rails.logger.info context[:time]
"Hello #{name}!"
end
Each field has a corresponding method which defines some actions that will be taken on the backend. For instance if you have a login query that has a username and password as parameters, the method will do something like look up and authenticate the user, create a session, etc.
Queries get a lot more interesting when you use them in combination with ActiveRecord objects. When you want to return data from an object in ActiveRecord, you can define a new type that defines fields and their corresponding methods from the class. An example would be something like this:
class Types::PersonType < Types::BaseObject
description "Defines a person"
field :id, ID, null: false
field :first_name, String, null: true
field :last_name, String, null: true
field :full_name, String, null: true
def full_name
"#{object.first_name} #{object.last_name}"
end
end
This was hard to wrap my head around at first because the root level fields in the query_type.rb
file also extend Types::BaseObject
, but I guess the way I think of it is that those are more general/ project level fields, whereas fields defined in their own Type classes are specifically for that ActiveRecord object. I grew to appreciate the way this is organized so everything isn’t jammed into one file.
Mutations are the other key component of GraphQL, and those are used to write/ update data. When the project is generated it creates a mutation_type.rb
file, containing fields and methods for mutations similar to how they are set up for queries. The methods for the fields in the mutation_type.rb
class are going to do some type of creating or updating of the underlying data.
You can simplify the creation of a mutation by creating an InputType, which defines a data structure that can be used as an input for the mutation, instead of having to list out every attribute of the object it should return.
class Types::PersonInputType < GraphQL::Schema::InputObject
graphql_name "PersonInputType"
description "All the attributes needed to create/ update a person"
argument :id, ID, required: false
argument :first_name, String, required: false
argument :last_name, String, required: false
argument :full_name, String, required: false
end
So then when you go to create a mutation to create a new instance of Person in this case, you can simply write:
mutation createPerson($person:PersonInputType!){
createPerson(person: $person) {
id
fullName
}
}
Of course this is assuming you have a field and method called “create_person” that look something like this:
module Types
class MutationType < Types::BaseObject
field :create_person, Types::PersonType, mutation: Mutations::CreatePerson
end
And then:
class Mutations::CreatePerson < GraphQL::Schema::Mutation
# nullability
null true
argument :person, Types::PersonInputType, required: true
def resolve(person:)
Person.create(person.to_h)
end
end
Overall I thought the Udemy course was excellent, and I would love to spend more time working with GraphQL. It’s a really fun and interesting way to interact with APIs.