Fetching External Data

Introduction

In this lesson, we will learn how to fetch data from external APIs and integrate it with our GraphQL server. This skill is crucial when building real-world applications, as data often resides in different places. By combining GraphQL with external APIs, you can create a more robust and comprehensive data layer in your applications.

Previously, you learned how to handle GraphQL mutations, manage complex queries, and set up real-time subscriptions. This lesson will build on those skills, focusing on fetching external data.

Defining the Schema

A GraphQL schema defines the types and the structure of queries. In Ruby, we define types as classes that inherit from GraphQL::Schema::Object. Here's the schema we'll use in this lesson:

Ruby
# book_type.rb
class BookType < GraphQL::Schema::Object
  field :id, ID, null: false
  field :title, String, null: false
  field :author, String, null: false
end

Here, we define a BookType class with fields id, title, and author. Each field is defined using the field method, specifying the field name, type, and whether it can be null.

Next, we define our query type:

Ruby
# query_type.rb
class QueryType < GraphQL::Schema::Object
  field :books, [BookType], null: false
  field :external_books, [BookType], null: false

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

  def external_books
    uri = URI('https://api.example.com/books')
    response = Net::HTTP.get(uri)
    JSON.parse(response)
  end
end

In the QueryType class, we define two fields: books and external_books. Each field has a corresponding resolver method that returns the data.

Notice that we define the field in Ruby using snake_case (external_books), which follows Ruby naming conventions. However, graphql-ruby automatically converts snake_case field names to camelCase when exposing them in the GraphQL schema. This means that when clients write queries, they'll use externalBooks (camelCase), even though the Ruby code uses external_books (snake_case). This automatic conversion applies to all field names throughout your schema — you always write Ruby-style snake_case in your code, and clients always use GraphQL-style camelCase in their queries.

Creating Resolvers

Resolvers define how to fetch data for each type in the GraphQL schema. We've learned about resolvers in previous lessons, but here's a quick reminder of their purpose.

In the code above, we've already defined our resolvers as methods within the QueryType class:

  • The books method returns a hardcoded array of book hashes.
  • The external_books method performs an HTTP request using Net::HTTP to get data from an external URL and then parses the JSON response using JSON.parse.

Let's look at the external_books resolver in more detail:

Ruby
def external_books
  uri = URI('https://api.example.com/books')
  response = Net::HTTP.get(uri)
  JSON.parse(response)
end

This method:

  1. Creates a URI object from the external API URL.
  2. Uses Net::HTTP.get to fetch the data from the URL.
  3. Parses the JSON response using JSON.parse and returns the result.

Setting Up the GraphQL Schema and Server

Now we need to create our GraphQL schema and set up a Sinatra server to handle requests.

First, let's create our schema:

Ruby
# schema.rb
require 'graphql'
require_relative 'book_type'
require_relative 'query_type'

class AppSchema < GraphQL::Schema
  query QueryType
end

This schema class ties together our query type and makes it available for execution.

Next, we'll set up our Sinatra server:

Ruby
# server.rb
require 'sinatra'
require 'json'
require 'net/http'
require_relative 'schema'

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

set :port, 4000

This code:

  • Sets up a POST endpoint at /graphql to handle GraphQL queries.
  • Parses the incoming JSON request to extract the query and variables.
  • Executes the query using our schema.
  • Returns the result as JSON.
  • Configures the server to run on port 4000.

To start the server, run:

Shell
ruby server.rb

Once running, you should see:

text
== Sinatra (v3.0.0) has taken the stage on 4000 for development with backup from Puma

Fetching Data from the Server

To test our server and fetch both local and external book data, we will write a simple Ruby script using Net::HTTP.

Ruby
# client.rb
require 'net/http'
require 'json'
require 'uri'

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

uri = URI('http://localhost:4000/graphql')
http = Net::HTTP.new(uri.host, uri.port)

request = Net::HTTP::Post.new(uri.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)

This script:

  • Defines a GraphQL query to fetch books and externalBooks. Remember, we use externalBooks (camelCase) here because graphql-ruby automatically exposes the Ruby-side external_books field as externalBooks in the GraphQL schema.
  • Creates an HTTP POST request to our running server.
  • Sends the query in the request body as JSON.
  • Parses and pretty-prints the response.

To run the client script:

Shell
ruby client.rb

Execution Output

If everything is set up correctly, running this script should produce an output similar to:

JSON
{
  "data": {
    "books": [
      {
        "id": "1",
        "title": "The Hobbit",
        "author": "J.R.R. Tolkien"
      },
      {
        "id": "2",
        "title": "Harry Potter",
        "author": "J.K. Rowling"
      }
    ],
    "externalBooks": [
      // Data fetched from the external API
    ]
  }
}

Lesson Summary

In this lesson, you learned how to define a schema using Ruby classes, create resolvers for both local and external data sources, set up a GraphQL server with Sinatra and graphql-ruby, and query the server. Here are the key points:

  • Defining GraphQL types as Ruby classes inheriting from GraphQL::Schema::Object.
  • Creating resolver methods within query type classes.
  • Fetching data from external APIs using Net::HTTP.
  • Integrating external APIs into your Sinatra GraphQL server.
  • Using Ruby's JSON module for parsing JSON responses.
  • Understanding that graphql-ruby automatically converts snake_case field names (Ruby convention) to camelCase (GraphQL convention) when exposing them in the schema.

Next, you will apply these concepts in hands-on practice exercises. Experiment with querying different external APIs and consolidating your knowledge.

Congratulations on making it this far. You're now well-equipped to handle external data in your GraphQL applications. Keep practicing to master these skills!

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