Ruby on Rails GraphQL API Tutorial: From 'rails new' to First Query

Isa Levine - Sep 16 '19 - - Dev Community

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 and Payment 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 and has_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

  1. Query -- this allows us to get data ("Read" in CRUD)
  2. 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 Payments 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 for id, description, and total, 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 all Types::PaymentType objects belonging to each Order.
  • However, Order does NOT have a payments_count column--so we define a payments_count() method to return an integer with the length of the payments array.
    • NOTE: inside these custom field methods, we need to access the Order's payments through object.payments--don't forget that critical object!

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 Orders 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 Orders.

Everything's now set! We can open up Insomnia and write our first Query to get all Orders 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:

screenshot of Insomnia running query and showing return data

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:

screenshot of Insomnia running query with fewer fields, and showing return data

Perfect! If we don't need the ids 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!

. . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player