Isa Levine
Posted on September 16, 2019
This week, I've been working on a takehome technical challenge asking me to deep-dive into GraphQL and create a simple API. I've never worked with GraphQL before, so I opted to stick with Ruby on Rails for learning it.
This tutorial is designed to walk through the steps to create a GraphQL API with Ruby on Rails and the Ruby gem 'graphql'.
It is largely adapted from this AMAZING tutorial by Matt Boldt, with a few notable differences:
I will use the Insomnia REST client for API calls instead of the 'graphiql' IDE -- if you don't already have it installed, go ahead and do that now!
I will start with simple
Order
andPayment
models, but eventually (in a future article) branch into more complicated relationships (and implementing features like filtering objects with custom methods directly on the model andhas_many
declaration)I will explore "idempotency" in GraphQL as part of Mutations (in a future article), using strategies covered in this EXCELLENT article by Todd Jefferson
Overview
In this first article, we'll go through the steps to:
- create a Rails API
- add some models
- add GraphQL
- write and execute our first GraphQL Query
GraphQL has two ways of interacting with databases
- Query -- this allows us to get data ("Read" in CRUD)
- Mutation -- this allows us to change information, including adding, updating, or removing data ("Create", "Update", "Destroy" in CRUD)
We'll keep our focus on getting the API running, and understanding our first simple Query.
Let's dive in!
What is GraphQL?
GraphQL is a query language we can use to get and mutate data in a database. It gives users a lot of control over what data you want to get back by targeting specific models and fields to return. It is also strongly typed, so you know exactly what kind of data you're receiving!
Read more about GraphQL on the project's website.
GraphQL is language-independent, so the Ruby implementation we will be using is the Ruby gem 'graphql'. This gives us a specific file structure and some command-line tools to easily add GraphQL functionality to our Rails API.
Creating the Rails app
'rails new'
Run the following command in your terminal to create a new Rails project called devto-graphql-ruby-api. Feel free to leave out any of these --skip flags, but none of them will be used:
$ rails new devto-graphql-ruby-api --skip-yarn --skip-action-mailer --skip-action-cable --skip-sprockets --skip-coffee --skip-javascript --skip-turbolinks --api
Generating models
Inside the directory, let's create our Order
and Payment
models:
$ rails g model Order description:string total:float
$ rails g model Payment order_id:integer amount:float
I prefer to set up my has_many-belongs_to relationships by hand, so let's make Payment
s belong to an Order
:
# app/models/order.rb
class Order < ApplicationRecord
has_many :payments
end
# app/models/payment.rb
class Payment < ApplicationRecord
belongs_to :order
end
Create database
Run $ rails db:create
to create the (default) SQLite3 development database.
Run migrations
Run $ rails db:migrate
to add our models to the database.
Add seed data
Add a few example objects to seed our database:
# db/seeds.rb
order1 = Order.create(description: "King of the Hill DVD", total: 100.00)
order2 = Order.create(description: "Mega Man 3 OST", total: 29.99)
order3 = Order.create(description: "Punch Out!! NES", total: 0.75)
payment1 = Payment.create(order_id: order1.id, amount: 20.00)
payment2 = Payment.create(order_id: order2.id, amount: 1.00)
payment3 = Payment.create(order_id: order3.id, amount: 0.25)
Then run $ rails db:seed
to add the data to the database.
Now we're ready to start adding in GraphQL on top of our models!
Adding GraphQL
Add 'graphql' gem to Gemfile
# Gemfile
gem 'graphql'
Then run $ bundle install
to install the gem in the app.
Install GraphQL with 'rails generate'
Run $ rails generate graphql:install
. This will add the /graphql/
directory to the app's main directory, as well as a GraphQL-specific controller at /controllers/graphql_controller.rb
.
Add GraphQL objects for models
We now need to create GraphQL objects to match our models:
$ rails generate graphql:object order
$ rails generate graphql:object payment
Filling out the new GraphQL files
Okay, we now have all the files and directories needed to build our first Query! But, some of those files still need some more code.
Define the GraphQL Types and their fields
GraphQL Types are defined with fields that tell us what data we can get from them:
# app/graphql/types/payment_type.rb
module Types
class PaymentType < Types::BaseObject
field :id, ID, null: false
field :amount, Float, null: false
end
end
This allows us to retrieve PaymentType
objects that contain an id
field (with a special ID primary key), and an amount
field that will be a Float. Because both are set to null: false
, receiving a Query response with nil
in either field will throw an error.
Our GraphQL objects inherit from Types::BaseObject. Thus, when we define our class PaymentType < Types::BaseObject
, we now have a Types::PaymentType
available. We can use these custom Types to define what we get back from each field.
Let's take a look at how we can use Types::PaymentType
in OrderType
:
# app/graphql/types/order_type.rb
module Types
class OrderType < Types::BaseObject
field :id, ID, null: false
field :description, String, null: false
field :total, Float, null: false
field :payments, [Types::PaymentType], null: false
field :payments_count, Integer, null: false
def payments_count
object.payments.size
end
end
end
Several things to note here:
- Because the
Order
model has columns forid
,description
, andtotal
, we can simply create a field for them and retrieve their data. - Because of our has_many-belongs_to relationship, we can also make a
payments
field to return allTypes::PaymentType
objects belonging to eachOrder
. - However,
Order
does NOT have apayments_count
column--so we define apayments_count()
method to return an integer with the length of thepayments
array.- NOTE: inside these custom field methods, we need to access the
Order
'spayments
throughobject.payments
--don't forget that criticalobject
!
- NOTE: inside these custom field methods, we need to access the
Define fields on QueryType
We're almost ready to write that first Query, but first, we need to tell the main QueryType to expect it. When GraphQL receives a Query request (as opposed to a Mutation request), it will be routed to the QueryType class. Like with the Types above, we will define possible Query methods through fields.
Our first Query will simply be to retrieve all Order
s in the database. Inside the class QueryType
declaration, we'll add a field that returns an array of Types::OrderType
:
# app/graphql/types/query_type.rb
module Types
class QueryType < Types::BaseObject
field :all_orders, [Types::OrderType], null: false
def all_orders
Order.all
end
end
end
As above, we define our all_orders()
method underneath the field with the same name, and tell it to implicity return all Order
s.
Everything's now set! We can open up Insomnia and write our first Query to get all Order
s back from the database.
Writing our first Query
GraphQL Query format
Here's what our first Query will look like:
query {
allOrders {
id
description
total
payments {
id
amount
}
paymentsCount
}
}
At the top, we define the request as a query {}
.
Inside the query, we call the QueryType's all_orders
via allOrders {}
. Yep, don't forget to switch from snake-case to camel-case!
Inside allOrders {}
, we select the fields from the Order
model we want returned. These are the same fields we defined in app/graphql/types/order_type.rb
. You can pick and choose which ones you want to receive!
Note that, with our payments {}
field, we also have to define the fields from Types::PaymentType
that we want to receive. The fields available are the ones we defined in app/graphql/types/payment_type.rb
.
The paymentsCount
field will run the payments_count
method on Types::OrderType
, and return the appropriate value.
Let's get this Query into Insomnia and test our API!
Execute Query in Insomnia
Run $ rails s
to start the Rails API server at http://localhost:3000/graphql.
Open Insomnia, and create a new POST request. In the top-left corner of the request text editor, make sure the the POST request's format is set to "GraphQL Query".
Go ahead and add the code from the query
above. Then send it off, and see what it returns:
Woo! Our data's all nice and organized--and it's exactly and ONLY what we requested!
Let's run a similar query, but with a fewer fields:
query {
allOrders {
description
total
payments {
amount
}
}
}
Result:
Perfect! If we don't need the id
s or the paymentsCount
, no need to include them in the Query at all!
Conclusion
We now have a very simple API to Query data from a database using GraphQL! However, since GraphQL Queries can only retrieve data, we can't use our current code to make any changes to the database.
That's where Mutations come in! We'll cover that in the next installment. ;)
Here's the repo for the code in this article, too. Feel free to tinker around with it!
And once again -- thank you to Matt Boldt and his AWESOME Rails GraphQL tutorial for helping me get this far! <3
Any tips or advice for using GraphQL in Rails, or GraphQL in general? Feel free to contribute below!
Posted on September 16, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 29, 2019