GraphQL server with Sinatra (Ruby) — Part 1

GraphQL has redefined the way we deal with APIs and endpoints. With the client being able to specify the fields it needs, development in the client could in one way be independent of the server changes, if a schema is predefined.

GraphQL + Sinatra — create a graphql endpoint in the lightweight micro framework.

We’ll see how to create a GraphQL server using the Sinatra framework in Ruby. In this example we will be creating the schema for a conference app, where you can add speakers and list them. Follow along the commit line at https://github.com/awinabi/sinatra-graphql/commits/master

TL;DR Steps to create a GraphQL endpoint that responds to a query to list Speakers.

STEP 1: Create a Sinatra application

I wish there was a template to create a sinatra application using a cli. But there isn’t a lot of boilerplate files to create, so lets add it one-by-one.

We’ll be using puma as the server. Create an app.rb file which defines a basic sinatra app with a / route. Also a Gemfile is added and bundle install is run. Refer the commit 16fd586

require 'sinatra'
class ConferenceApp < Sinatra::Base
  get '/' do
'It Works!'
end
end

Next we need a rackup file config.ru so that puma will pickup the app as a rack application. (Refer the step1 branch).

require './app'
run ConferenceApp

Running the puma server will serve your application at http://localhost:9292, and you should see the message ‘It Works!’.

bundle exec puma

Yay! The app is up.

STEP 2: Add JSON responses

For the server to respond to JSON, we add the sinatra-contrib gem, which adds a json helper. Change the app.rb file to respond to json.

#file app.rb
require 'sinatra/json'
#...
get '/hello.json' do
message = { success: true, message: 'hello'}
json message
end
#...

Now our app contains just these files (Refer step2 branch):

conference_app
|
├── Gemfile
├── Gemfile.lock
├── app.rb
└── config.ru

STEP 3: Add database connections and models with ActiveRecord

For talking to the database, we’ll use activerecord gem.

Add database configuration to connect to sqlite3

Also add a configuration file database.yml (commit efe8d9c) with the connection details and the sqlite3 gem for connecting to the sqlite database. app.rb needs changes to update this configuration.

#changes in app.rb
# ...
require 'sinatra/activerecord'
# ...
class ConferenceApp < Sinatra::Base
set :database_file, 'config/database.yml'
# ...
# ...
end

Add Rakefile

Add the rake gem along with the Rakefile (Commit: cc5f6af). This gives handy rake tasks for creating the table (migrations) and managing them.

# Rakefile
require './app'
require 'sinatra/activerecord/rake'

bundle exec rake -T will display the added rake tasks.

Create the sqlite database, by running bundle exec rake db:create

Add a migration and model for Speaker object

Create a migration with the following rake command:

bundle exec rake db:create_migration NAME=create_speakers

Change the created migration file in db/migrate folder, to add the required database fields.

class CreateSpeakers < ActiveRecord::Migration[5.1]
def change
create_table :speakers do |t|
t.string :name, null: false
t.string :twitter_handle
t.text :bio
t.string :talk_title
end
end
end

Run migrations with the rake task bundle exec rake db:migrate

Create a model file for Speaker, to access this table.

# file models/speaker.rb
class Speaker < ActiveRecord::Base
validates :name, presence: true
end

I’ve added a basic validation for the model. Read more on activerecord basics in the official basics introduction.

Add the pry gem for debugging (8ef9609) and execute the following 2 create statements for adding rows to the speakers table.

Use pry to create records for Speaker model.
require './app'
Speaker.create(name: 'John', twitter_handle: 'johnruby', bio: 'This is John\'s bio', talk_title: 'How to bootstrap a sinatra application')
Speaker.create(name: 'Jacob', twitter_handle: 'jacob-ruby', bio: 'This is Jacob\'s bio', talk_title: 'Introduction to graphql')

Add a /speakers endpoint

Create a new endpoint to show the list of speakers, as JSON.

# Changes to file app.rb
# ...
require 'sinatra/activerecord'
require_relative 'models/speaker'
class ConferenceApp < Sinatra::Base
# ...
  get '/speakers' do
@speakers = Speaker.all
json @speakers
end
end

Refer the step3 branch.

STEP 4: Add graphql and define a query to list speakers

Now we have a sinatra app that connects to the database and shows a list of speakers as a JSON response. Now let’s add graphql and define a schema for speakers.

Add the graphql gem. https://github.com/rmosolgo/graphql-ruby

Also the rack-contrib gem needs to be added so that the sinatra app can accept raw JSON payloads. (Commit 700f21c)

Add type, query and schema for graphql

Now we need to add a type for Speaker (commit e7c669c), also a query and a schema for GraphQL.

#File graphql/types/speaker_type.rb
require 'graphql'
module Types
SpeakerType = GraphQL::ObjectType.define do
name 'Speaker'
description 'Resembles a Speaker Object Type'
    field :id, !types.ID
field :name, types.String
field :twitterHandle, types.String, property: :twitter_handle
field :bio, types.String
field :talkTitle, types.String, property: :talk_title
end
end

We need to then add a root query(commit 8754050)

# File graphql/query.rb
require 'graphql'
require_relative 'types/speaker_type'
QueryType = GraphQL::ObjectType.define do
name "Query"
description "The query root of this schema"
  field :speakers, types[Types::SpeakerType] do
description "Get a list of speakers"
    resolve ->(_obj, _args, _ctx) {
Speaker.all
}
end
end

Define a schema for GraphQL.

# File graphql/schema.rb
require 'graphql'
require_relative 'query'
ConferenceAppSchema = GraphQL::Schema.define do
query QueryType
end

The /graphql endpoint

We now need to have a POST endpoint for GraphQL.

GraphQL schema can be executed to give a GraphQL::Query::Result which can then be converted to JSON. app.rb needs change to include this endpoint. (Commit b78b6af).

# Changes to file app.rb
# ...
require 'rack/contrib'
class ConferenceApp < Sinatra::Base 
# ...
  use Rack::PostBodyContentTypeParser 
# ...
# ...
post '/graphql' do
result = ConferenceAppSchema.execute(
params[:query],
variables: params[:variables],
context: { current_user: nil },
)
json result
end
# ...
end

Querying the endpoint

You can use the graphiql app (https://github.com/skevy/graphiql-app) or the Postman app (https://www.getpostman.com/) to query the endpoint. Make sure that you have puma running and the server is up.

Querying the graphql endpoint in Postman

A JSON response like the below will be obtained.

{
"data": {
"speakers": [
{
"name": "John",
"twitterHandle": "johnruby",
"bio": "This is John's bio"
},
{
"name": "Jacob",
"twitterHandle": "jacob-ruby",
"bio": "This is Jacob\\’s bio"
}
]
}
}

That’s it for now. You have a GraphQL server up and running on sinatra, and you can query the endpoint to get a list of speakers with the fields defined in the GraphQL query.

Source Code

The source code is available at https://github.com/awinabi/sinatra-graphql

You can follow along the commits, and refer to the branches for each step. Please star the repo if you like the post! Thanks.

In Part 2 of this series, we’ll see how to add a mutation to add a new Speaker row, and deploy it to Heroku.

Like what you read? Give Awin Abi a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.