Ruby + GraphQL

Uchenna Awa
5 min readJun 21, 2023

--

In the world of modern web development, APIs play a crucial role in connecting different components and delivering data efficiently. Traditional REST APIs have been the go-to choice, but there’s a rising star that offers even more flexibility and efficiency: GraphQL. When combined with the power and expressiveness of Ruby, developers have an exceptional toolkit for building robust and scalable APIs.

GraphQL is an open-source query language and runtime for APIs (Application Programming Interfaces). It was developed by Facebook and later released as an open-source project in 2015. GraphQL provides a more efficient and flexible alternative to traditional REST APIs.In this article, we’ll explore the synergy between GraphQL and Ruby, uncovering the best practices, tools, and techniques to implement GraphQL in Ruby applications. Whether you’re new to GraphQL or an experienced Ruby developer, this guide will equip you with the knowledge to leverage these technologies effectively and create APIs that exceed expectations.

In summary, with GraphQL the client structures the data in the request and the response contains only the requested data in the same structure

Join me on this journey as I dive into GraphQL and Ruby, discovering how they complement each other and how their combined potential can revolutionize your API development process. From setting up a GraphQL server to optimizing performance, securing your API, and integrating with Ruby on Rails, we’ll cover it all.

Prepare to unlock the power of GraphQL and Ruby, and embark on a path to building powerful APIs the right way. Let’s get started!

First, let’s create an app for delivery riders, where each rider has a location and an address.

To create a Ruby on Rails API-only app for riders with associated locations and addresses, you can follow these steps:

Create a new Rails application:

$ rails new riders_api - API
$ cd riders_api

Then, generate the Rider, Location, and Address models:

$ rails generate model Rider first_name last_name email phone_number password_digest
$ rails generate model Location latitude:float longitude:float rider:references
$ rails generate model Address street:string city:string state:string zip:string rider:references

Set up the database and run migrations:

$ rails db:create
$ rails db:migrate

In the respective model files (rider.rb,location.rb, address.rb), add the associations:

# app/models/rider.rb
class Rider < ApplicationRecord
has_one :location
has_one :address
end

# app/models/location.rb
class Location < ApplicationRecord
belongs_to :rider
end

# app/models/address.rb
class Address < ApplicationRecord
belongs_to :rider
end

Generate the Riders controller:

$ rails generate controller Riders

In the app/controllers/riders_controller.rb file, define the controller actions:

class RidersController < ApplicationController
def index
riders = Rider.all
render json: riders
end

def show
rider = Rider.find(params[:id])
render json: rider
end
# Implement other CRUD actions (create, update, destroy) as needed
end

In the config/routes.rb file, add the routes:

Rails.application.routes.draw do
resources :riders, only: [:index, :show]
# Add routes for other actions as needed
end

Start the Rails server:

$ rails server

Use an API testing tool like Postman or cURL to send requests to the API endpoints. For example:
- GET request to https://localhost:3000/riders to retrieve all riders.
- GET request to https://localhost:3000/riders/:id to retrieve a specific rider.

Adding GraphQL

First, we need to add GraphQL to our bundle

gem 'graphql'
gem 'graphiql-rails'

Install the gems by running the following command in your project’s root directory and generate the GraphQL files and directories by running the following command:

$ bundle install
$ rails generate graphql:install

This command will create the necessary directories and files for GraphQL, including the GraphQL schema file (app/graphql/types and app/graphql/mutations directories).

Configure the routes to expose the GraphQL endpoint by adding the following line to your config/routes.rb file:

Rails.application.routes.draw do
post '/graphql', to: 'graphql#execute'

resources :riders, only: [:index, :show]
# Add routes for other actions as needed
end

For each model, we need to create graphql query type, in app/graphql/type This can do this by running the following commands

$ rails g graphql:object Rider
$ rails g graphql:object Location
$ rails g graphql:object Address

Three files would be generated for each model, however, we would need to edit the rider_type.rb to add the location and address relationships

module Types
class RiderType < Types::BaseObject
...

field :location, Types::LocationType
field :address, Types::AddressType

...
end
end

Important note: Type in square brackets depicts an array. e.g. [RiderType] would return an array but, RiderType is expected to return one RiderType. so if the rider has_many addresses use field :addresses, [Types::AddressType]

Then, we need to define all the available fields in the query_type.rb file

module Types
class QueryType < Types::BaseObject
...

# to define the riders and rider fields
field :riders, [Types::RiderType], null: false
field :rider, Types::RiderType, null: false do
argument :id ID
end

# to define the locations and location fields
field :locations, [Types::LocationType], null: false
field :location, Types::LocationType, null: false do
argument :id ID
end

# to define the addresses and address fields
field :addresses, [Types::AddressType], null: false
field :address, Types::AddressType, null: false do
argument :id ID
end

def riders
Rider.all
end

def rider(id:)
Rider.find(id)
end

# include definitions for locations, addresses and their individual methods
...
end
end

At this point, the application is ready to handle GraphQL requests

{
riders(id: 2){
firstName
lastName
location {
latitiude
longitude
}
address {
street
city
state
}
}
}

The response should be as expected

{
"data": {
"riders": [
{
"firstName": "John",
"lastName": "Doe",
"location": {
"latitude": "0.453433",
"longitude": "0.56342",
},
"address": {
"street": "0.453433",
"city": "0.56342",
"state": "0.56342",
},
},
{
"firstName": "Jane",
"lastName": "Doe",
"location": {
"latitude": "0.358966",
"longitude": "0.73542",
},
"address": {
"street": "0.453433",
"city": "0.56342",
"state": "0.56342",
},
},
]
}
}

We can stop here

However, it is worth considering that the query_type.rb file may become verbose and repetitive as the number of fields increases. In order to address this concern, it is crucial to explore potential solutions to dynamically generate the query type fields. I have devised a solution that effectively handles this issue:

module Types
class QueryType < Types::BaseObject
# Add `node(id: ID!) and `nodes(ids: [ID!]!)`
include GraphQL::Types::Relay::HasNodeField
include GraphQL::Types::Relay::HasNodesField

# # Creating methods from a list of model names ##
# to define methods based on the names in this array
model_names = ActiveRecord::Base.connection.tables.reject! { |element| element == "schema_migrations" || element == "ar_internal_metadata" }

# loop through array names and use 'define_method(name)'
model_names.each do |name|
field_name = "Types::"+name.classify+"Type"
field name.to_sym, [field_name.constantize], null:false

field name.singularize.to_sym, field_name.constantize, null: false do
argument :id, ID
end

define_method(name) do
name.classify.constantize.all
end

define_method(name.singularize) do |id:|
name.classify.constantize.find(id)
end
end
end
end

This gets an array of all tables in the database, loops through it and creates the respective fields and definitions.

--

--