Setting up GraphQL with Ruby on Rails

Nicky Liu
Future Vision
Published in
6 min readJun 4, 2019

Restful APIs are pretty useful, but there is often the problem of over-fetching. The only way to get the name of a person is to fetch a person and get all of their attributes, including needless information like height, gender, race, etc. This is where graphQL comes in! In graphQL you have the ability to fetch specific attributes only, so say goodbye to fetching too much data! Today I will go over the basics of how to create a graphQL backend.

Start by creating a file.

rails new sample-graphqlcd sample-graphqlrails db:createrails s

Add gem “graphql” to the gem file.

bundle installrails generate graphql:installbundle install

Create some sample data. For example using Yu-Gi-OH: duelist, decks, and cards. The relationship

Generate a model for players.

rails generate model duelist name rank:integer

Generate a model for cards.

rails generate model card name attack:integer defense:integer duelist_id:integer

Generate a model for decks.

rails generate model deck name duelist_id:integer

For decks and cards however, the relationship is a little different. Intuitively you might say a deck has many cards, which would be accurate since a deck generally contains 40 cards. However, in all Yu-Gi-Oh games you can make multiple decks, and you can re-use cards that were in a previous deck. (Imagine needing 6 copies of an ultra-rare card if you wanted to include 3 copies of it in two different decks?) So a card can also belong to many decks. Decks have many cards and card have many decks, so you need a join table for the two.

rails generate model deckcard deck_id:integer card_id:integer

Now run:

rails db:migrate

Now we need to add the relationships to the models.

For duelists.

class Duelist < ApplicationRecord
has_many :cards
has_many :decks
end

For cards.

class Card < ApplicationRecord
belongs_to :duelist
has_many :deckcards
has_many :decks, through: :deckcards
end

For decks.

class Deck < ApplicationRecord
belongs_to :duelist
has_many :deckcards
has_many :cards, through: :decks
end

For deckcards.

class Deckcard < ApplicationRecord
belongs_to :card
belongs_to :deck
end

Now let’s create some seed data in db > seed.db.

duelist1 = Duelist.create(name: "Yugi", rank: 1)
duelist2 = Duelist.create(name: "Kaiba", rank: 2)
duelist3 = Duelist.create(name: "Joey", rank: 3)
card1 = Card.create(name: "Dark Magician", attack: 2500, defense: 2100, duelist_id: duelist1.id)
card2 = Card.create(name: "Blue-Eyes White Dragon", attack: 3000, defense: 2500, duelist_id: duelist2.id)
card3 = Card.create(name: "Red-Eyes Black Dragon", attack: 2400, defense: 2000, duelist_id: duelist3.id)
deck1 = Deck.create(name: "Yugi Deck", duelist_id: duelist1.id)
deck2 = Deck.create(name: "Kaiba Deck", duelist_id: duelist2.id)
deck3 = Deck.create(name: "Joey Deck", duelist_id: duelist3.id)
deck4 = Deck.create(name: "Joey Deck 2", duelist_id: duelist3.id)
Deckcard.create(deck_id: deck1.id, card_id: card1.id)
Deckcard.create(deck_id: deck2.id, card_id: card2.id)
Deckcard.create(deck_id: deck3.id, card_id: card3.id)
Deckcard.create(deck_id: deck4.id, card_id: card3.id)

Now we need to create graphQL object types.

rails generate graphql:object duelist
rails generate graphql:object card
rails generate graphql:object deck

Now we need to fill out the duelist, card, and deck type files. graphQL > types > duelist_type.rb

module Types
class DuelistType < GraphQL::Schema::Object
field :id, ID, null: false
field :name, String, null: false
field :rank, Integer, null: false
field :cards, [CardType], null: true
field :decks, [DeckType], null: true
end
end

By default it assumes a value can be null, so if a field is mandatory write null: false next to it.

Do the same for deck_type.rb:

module Types
class DeckType < GraphQL::Schema::Object
field :id, ID, null: false
field :name, String, null: false
field :duelist, DuelistType, null: false
field :cards, [CardType], null: true
end
end

And the same again for card_type.rb:

module Types
class CardType < GraphQL::Schema::Object
field :id, ID, null: false
field :name, String, null: false
field :attack, Integer, null: true
field :defense, Integer, null: true
field :duelist, DuelistType, null: false
field :decks, [DeckType], null: true
end
end

Now for the query form. This is basically the graphQL version of the controllers when making a restful api.

graphQL > types > query_type.rb

Write a query for getting all duelists:

module Types
class QueryType < Types::BaseObject
description "root query"
field :duelists, [DuelistType], null: true do
description "gets all duelists"
end
def duelists
Duelist.all
end
end
end

Now go to http://localhost:3000/graphiql

The left side is where you can enter queries to retrieve data. Try entering:

{
duelists {
name
rank
id
cards {
name
attack
defense
}
decks {
name
}
}
}

On the right side you will get back an array of all the duelists, as well as their name, rank, and an array for their cards and decks.

To write a query for a single duelist add the following lines to query_type.rb

field :duelist, DuelistType, null: true do
description "find duelist by id"
argument :id, ID, required: true
end
def duelist(id:)
Duelist.find(id)
end

Notice the change from “field :duelists, [DuelistType], null: true do” to “field :duelist, DuelistType, null: true do”. This is because the query to get all duelists returns an array of them all, while the query to find a single duelist returns only that duelist as an object.

To do a single query go back to http://localhost:3000/graphiql and for the query enter:

{
duelist(id: 1) {
name
rank
id
cards {
name
attack
defense
}
decks {
name
}
}
}

You should get back only a single duelist object with the id of 1, Yugi in this case.

Add the same all search and single search queries for cards and decks. It should end up looking like:

module Types
class QueryType < Types::BaseObject
description "root query"
field :duelists, [DuelistType], null: true do
description "get all duelists"
end
field :duelist, DuelistType, null: true do
description "find duelist by id"
argument :id, ID, required: true
end
field :cards, [CardType], null: true do
description "get all cards"
end
field :card, CardType, null: true do
description "find card by id"
argument :id, ID, required: true
end
field :decks, [DeckType], null: true do
description "get all decks"
end
field :deck, DeckType, null: true do
description "find deck by id"
argument :id, ID, required: true
end
def duelists
Duelist.all
end
def duelist(id:)
Duelist.find(id)
end
def cards
Card.all
end
def card(id:)
Card.find(id)
end
def decks
Deck.all
end
def deck(id:)
Deck.find(id)
end
end
end

You can now run all or single queries on each of them.

We now need to build for mutations, which allows us to post, modify, and delete items from the database. Create a root mutation.

touch app/graphql/mutations/base_mutation.rb

Inside write the following code:

class Mutations::BaseMutation < GraphQL::Schema::Mutationend

Now we need to create a mutation to create, update, and delete duelists, cards, and decks.

rails g graphql:mutation create_duelist.rb
rails g graphql:mutation update_duelist.rb
rails g graphql:mutation create_card.rb
rails g graphql:mutation update_card.rb
rails g graphql:mutation delete_card.rb
rails g graphql:mutation create_deck.rb
rails g graphql:mutation update_deck.rb
rails g graphql:mutation delete_deck.rb

Modify the create, update, and delete files for each one.

For create duelist:

module Mutations
class CreateDuelist < GraphQL::Schema::RelayClassicMutation
field :duelist, Types::DuelistType, null: false
argument :name, String, required: true
argument :rank, Integer, required: false
def resolve(name:, rank:)
duelist = Duelist.create!(name: name, rank: rank)
{
duelist: duelist
}
end
end
end

Update duelist:

module Mutations
class UpdateDuelist < GraphQL::Schema::RelayClassicMutation
field :duelist, Types::DuelistType, null: false
argument :id, ID, required: true
argument :name, String, required: true
argument :rank, Integer, required: false
def resolve(id:, name:, rank:)
duelist = Duelist.find(id)
duelist.update!(name: name, rank: rank)
{
duelist: duelist
}

end
end
end

Delete duelist:

module Mutations
class DeleteDuelist < GraphQL::Schema::RelayClassicMutation
field :duelist, Types::DuelistType, null: false
argument :id, ID, required: true def resolve(id:)
duelist = Duelist.find(id)
duelist.destroy!
{
duelist: duelist
}
end
end
end

Do the equivalent for all three, changing the class names and attributes to fit to the corresponding model.

You can try adding a duelist by running the following query:

mutation {
createDuelist(input: {name: "Marik", rank: 5}){
duelist {
name,
rank
}
}
}

Now if you fetch all duelists again with

{
duelists {
name
rank
cards {
name
attack
defense
}
decks {
name
}
}
}

You will see that our new duelist, Marik has been added to the list!

To update a duelist enter:

mutation {
updateDuelist(input: {id: 5, name: "Zane", rank: 1}){
duelist {
name,
rank
}
}
}

If you get all duelists again you will see that duelist with id number 4, formerly known as Marik, will now have his name changed to “Zane” and his rank changed to 1.

To delete the newest duelest enter:

mutation {
deleteDuelist(input: {id: 4}){
duelist {
name,
rank
}
}
}

Now if you get all duelists again you will see that Zane is gone, and only the original one are left.

Congratulations, your graphQL API is all set up! We have successfully set up a backend api where seed data can be created, objects can be fetched (and specific attributes from them can be fetched too!), and objects can be added, updated, or destroyed! My next article will go into how to connect this to a react app using Apollo!

--

--