Nested Queries in GraphQL

Introduction And Context Setting

Welcome to the lesson on handling more complex data queries as part of the "Comprehensive Intro to GraphQL in Ruby" course. In the previous lesson, you learned how to set up a GraphQL server and define mutations to modify data. In this lesson, we'll shift focus to reading data in more sophisticated ways. Specifically, we'll set up nested queries in GraphQL to handle intricate relationships between data types, such as authors and books.

Defining The Schema With Nested Queries

To handle nested queries, we need a schema that represents our data types and their relationships.

  1. Define Data Types.

    We'll create Author and Book types with fields that reference each other:

    Ruby
    class AuthorType < GraphQL::Schema::Object
      field :id, ID, null: false
      field :name, String, null: false
      field :books, [BookType], null: true
    end
    
    class BookType < GraphQL::Schema::Object
      field :id, ID, null: false
      field :title, String, null: false
      field :author, AuthorType, null: true
    end
    • AuthorType has fields id, name, and books. The [BookType] syntax means this field returns an array of BookType objects. Note that null: true here means the entire list can be null (i.e., the field itself may return nil), not that individual books within the list can be null. If you wanted to allow null elements inside a non-null array, you would write [BookType, null: true], null: false instead.
    • BookType has fields id, title, and an author, which is of type AuthorType. Here, null: true means the author field itself can be null (e.g., if a book has no associated author).
  2. Sample Data.

    Define some sample data to work with:

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

    This data will be used to simulate a small library. Notice that each book stores its author as an id string (e.g., '1'), not as a full author hash. The resolvers we define next will be responsible for looking up the actual author data from this ID.

Implementing Resolvers For Nested Queries

Resolvers are responsible for fetching the data defined in your schema.

  1. Define Resolvers.

    Here's how you can write resolvers to handle nested queries:

    Ruby
    class BookType < GraphQL::Schema::Object
      field :id, ID, null: false
      field :title, String, null: false
      field :author, AuthorType, null: true
    
      def author
        AUTHORS.find { |author| author[:id] == object[:author] }
      end
    end
    
    class AuthorType < GraphQL::Schema::Object
      field :id, ID, null: false
      field :name, String, null: false
      field :books, [BookType], null: true
    
      def books
        BOOKS.select { |book| book[:author] == object[:id] }
      end
    end
    
    class QueryType < GraphQL::Schema::Object
      field :books, [BookType], null: false
      field :authors, [AuthorType], null: false
    
      def books
        BOOKS
      end
    
      def authors
        AUTHORS
      end
    end
    • The QueryType resolvers return the sample data for books and authors.
    • The BookType resolver finds the author of a given book.
    • The AuthorType resolver filters books written by a given author.

    You'll notice that the resolver methods use object — for example, object[:author] and object[:id]. In graphql-ruby, object is a built-in method available inside every type class. It refers to the underlying Ruby data (in our case, a hash) that the current GraphQL type is wrapping. So when GraphQL is resolving a specific BookType, object is the book hash (e.g., { id: '1', title: 'The Hobbit', author: '1' }), and object[:author] retrieves the author's ID from that hash. Similarly, inside AuthorType, object is the author hash, and object[:id] gives you that author's ID.

  2. Initialize The Schema And Server.

    Combine the types to set up the schema and server:

    Ruby
    class LibrarySchema < GraphQL::Schema
      query QueryType
    end
    
    post '/graphql' do
      request_payload = JSON.parse(request.body.read)
      query = request_payload['query']
      variables = request_payload['variables'] || {}
    
      result = LibrarySchema.execute(query, variables: variables)
      content_type :json
      result.to_json
    end

    When you run your graphql_ruby/main.rb file, it should print:

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

Testing The Nested Queries

Now that your server is up and running, let's test the nested queries using a real-world example.

  1. Define The Query.

    In a file called run.rb, write a query to fetch books and their authors:

    Ruby
    require 'net/http'
    require 'json'
    
    def fetch_books_and_authors
      query = <<~GRAPHQL
        query {
          books {
            title
            author {
              name
            }
          }
          authors {
            name
            books {
              title
            }
          }
        }
      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
    
      begin
        response = http.request(request)
        data = JSON.parse(response.body)
        puts JSON.pretty_generate(data)
      rescue StandardError => e
        puts "Error: #{e.message}"
      end
    end
    
    fetch_books_and_authors
  2. Run The Query.

    Running this function should give you the following output:

    JSON
    {
      "data": {
        "books": [
          {
            "title": "The Hobbit",
            "author": {
              "name": "J.R.R. Tolkien"
            }
          },
          {
            "title": "Harry Potter",
            "author": {
              "name": "J.K. Rowling"
            }
          }
        ],
        "authors": [
          {
            "name": "J.R.R. Tolkien",
            "books": [
              {
                "title": "The Hobbit"
              }
            ]
          },
          {
            "name": "J.K. Rowling",
            "books": [
              {
                "title": "Harry Potter"
              }
            ]
          }
        ]
      }
    }

    This confirms the server correctly handles your nested queries and returns the expected data.

Summary And Next Steps

In this lesson, you've learned how to handle more complex data queries in GraphQL by setting up nested queries with graphql-ruby and Sinatra. You've defined a schema with nested types, implemented resolvers, and tested your queries.

Now, it's time to practice what you've learned. Head over to the practice exercises to solidify your understanding. Try experimenting with more complex queries and relationships to gain a deeper grasp of handling data in GraphQL.

Congratulations on making it this far! Keep practicing to reinforce your newfound 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