Integrating GraphQL with Sinatra

Introduction and Context Setting

Welcome to this lesson on integrating graphql-ruby with Sinatra! In previous lessons, you learned about setting up a basic GraphQL server, defining types and queries, and querying with arguments. In this lesson, you'll learn how to integrate graphql-ruby with Sinatra, a popular web application framework for Ruby.

Creating the GraphQL Schema and Resolvers

Type definitions define the shape of your data and the operations that can be performed. For example, you might define a book type and a query type to specify how to fetch books.

Let's define our schema using graphql-ruby's class-based approach:

class BookType < GraphQL::Schema::Object
  field :id, ID, null: false
  field :title, String, null: false
  field :author, String, null: false
end

Next, we define our query type with resolvers:

class QueryType < GraphQL::Schema::Object
  field :books, [BookType], null: false
  field :book, BookType, null: true do
    argument :id, ID, required: true
  end

  def books
    BOOKS
  end

  def book(id:)
    BOOKS.find { |book| book[:id] == id }
  end
end

Finally, we create our schema:

class AppSchema < GraphQL::Schema
  query QueryType
end

In this code, we define a BookType with three fields: id, title, and author. The QueryType defines two query fields: books, which returns all books, and book, which takes an id argument and returns a specific book. The resolver methods fetch the data from our BOOKS array.

Integrating graphql-ruby with Sinatra

Let's create a basic Sinatra app and integrate graphql-ruby:

require 'sinatra'
require 'graphql'
require 'json'

BOOKS = [
  { id: '1', title: 'The Hobbit', author: 'J.R.R. Tolkien' },
  { id: '2', title: 'Harry Potter', author: 'J.K. Rowling' }
]

post '/graphql' do
  request_body = JSON.parse(request.body.read)
  query = request_body['query']
  variables = request_body['variables'] || {}
  
  result = AppSchema.execute(query, variables: variables)
  
  content_type :json
  result.to_json
end

In this code block, we set up a POST route at /graphql that handles GraphQL requests. When a request comes in, we parse the JSON body to extract the query and variables. We then execute the query using our AppSchema and return the result as JSON. The content_type :json ensures the response has the correct content type header.

To start the server, we can add:

set :port, 4000
set :bind, '0.0.0.0'

This configures Sinatra to run on port 4000 and bind to all network interfaces.

Example Queries and Testing: Query Definition

After we have defined our server and started it, let's do some queries.

First, we define our queries:

QUERY_BOOKS = <<~GRAPHQL
  query {
    books {
      id
      title
      author
    }
  }
GRAPHQL

def query_book_by_id(id)
  <<~GRAPHQL
    query {
      book(id: "#{id}") {
        id
        title
        author
      }
    }
  GRAPHQL
end

Here, we define two queries using Ruby's heredoc syntax for multi-line strings:

  1. QUERY_BOOKS: A simple query that fetches all books. It doesn't require any arguments and returns the full list with each book's id, title, and author.

  2. query_book_by_id(id): This is where arguments come into play. Notice the book(id: "#{id}") syntax - we're passing an argument to the book field. The #{id} is Ruby string interpolation that inserts the ID value into the query string. For example, if we call query_book_by_id('1'), it generates a query that looks like book(id: "1"). This tells GraphQL to find and return only the book with that specific ID, rather than fetching all books and filtering on the client side.

Example Queries and Testing: Fetch the Data

Next, we use Net::HTTP to execute these queries:

require 'net/http'
require 'json'

url = URI('http://localhost:4000/graphql')

def fetch_data(url, query)
  begin
    http = Net::HTTP.new(url.host, url.port)
    request = Net::HTTP::Post.new(url.path, 'Content-Type' => 'application/json')
    request.body = { query: query }.to_json
    
    response = http.request(request)
    data = JSON.parse(response.body)
    puts JSON.pretty_generate(data)
  rescue StandardError => e
    puts "Error: #{e.message}"
  end
end

def run_queries(url, QUERY_BOOKS, query_book_by_id)
  fetch_data(url, QUERY_BOOKS)
  fetch_data(url, query_book_by_id.call('1'))
end

run_queries(url, QUERY_BOOKS, method(:query_book_by_id))

The fetch_data method sends a POST request to our GraphQL endpoint with the specified query. It creates an HTTP connection, sets up the request with proper headers, sends the query as JSON, and prints the formatted response. The run_queries method executes our defined queries.

Example Queries and Testing: Output

Running the previous code should give you output like:

{
  "data": {
    "books": [
      {
        "id": "1",
        "title": "The Hobbit",
        "author": "J.R.R. Tolkien"
      },
      {
        "id": "2",
        "title": "Harry Potter",
        "author": "J.K. Rowling"
      }
    ]
  }
}
{
  "data": {
    "book": {
      "id": "1",
      "title": "The Hobbit",
      "author": "J.R.R. Tolkien"
    }
  }
}

Which corresponds with the data we initially defined.

Summary

In this lesson, you learned how to integrate graphql-ruby with Sinatra, create a GraphQL schema and resolvers, and run your server. You've also seen examples of executing queries to fetch data. Let's go and practice now!

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal