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!
Top comments (11)
Hi Isa, thanks for the great post. I was following it step by step, however for some reason I am running into an "ActionController::RoutingError (No route matches [GET] "/graphql")" error. Do you know which step might causing the issues? Thank you!
Hi Jose-Xu! At which step are you seeing this error? I'd guess it's probably from trying to access
localhost:3000/graphql
in your browser? If so, I believe the issue is that the/graphql
route only accepts a POST request.Let me know which part you're getting that error at, and I'll see what I can do to help! :)
Hi Isa, thanks for your reply, and sorry for the late reply, since I do not get any notifications. And yes the problem is exactly like what you said it appears as I trying to access localhost:3000/graphql. and it seems like it is getting a GET request. However in the route.rb I have wrote post "/graphql", to: "graphql#execute". Do you know if there are any other places that I might have made a mistake? Much appreciate!
Hi Jose-Xu, no problem at all on the late reply! :)
It might help to break down the expected behavior. When you say you are trying to access
localhost:3000/graphql
, do you mean:The only way to access the endpoint at
localhost:3000/graphql
is by sending a POST request (in this tutorial's case, through Insomnia) and have the requests body be formatted as a GraphQL Query. So, trying to accesslocalhost:3000/graphql
from a browser will always return an error because it's never expecting a GET request.On the other hand, if your issue is that you're getting errors with sending a POST request with a GraphQL Query body, then the issue is probably with the request body itself! Are you using Insomnia (or something similar, like Postman) to send the POST request? And if so, can you send a screenshot so we can look over it? :)
Hi Isa, I actually found my issues, as I typed something wrong in my query_type.rb file. Thanks for made it clear that I should not have expect localhost:3000/graphql to work in the browser!
Glad to hear it Jose-Xu, and well done!
I was recreating the API step-by-step, and thought the issue might be that the GraphQL gem's
rails generate graphql:install
wasn't auto-creating all the files it needed to--and that would've been a WAY BIGGER PROBLEM! Very happy it was something less drastic than that. :)thank you so much for help again!!!!
Hi I have the same error as Jose-Xu, Don't know when you made it, have you tried again?
dev.to/qz135636665908/comment/oo7f
quote:
"Hi Isa, I actually found my issues, as I typed something wrong in my query_type.rb file. Thanks for made it clear that I should not have expect localhost:3000/graphql to work in the browser!"
I don't know what error he made in query_type.rb file but I checked it very well and I got it right!
can you check this problem ? thanks
Hi oTangVinhDuong! If I'm not mistaken, I believe Jose-Xu's problem was trying to access localhost:3000/graphql in a browser.
Since GraphQL relies on sending POST requests only, you will need to use a tool like Insomnia, Postman, or graphiql to view the query results.
To double-check everything is working, I just used Insomnia to send a POST request to localhost:3000/graphql/ using a GraphQL body with the following query:
I also confirmed that the
query_type.rb
file I used just now matches what is above--this is the code I just ran:Let me know if you're still having issues, and I'll try to help as best I can! :)
Great post. Thank you so much.