Advanced GraphQL Arguments

Introduction and Overview

In this lesson, we will build upon your existing GraphQL skills by introducing advanced query and mutation arguments. These techniques will enable you to create more flexible and powerful APIs. Advanced arguments allow for greater precision in the data you request and the operations you perform.

Defining Advanced Schema with Arguments

Let's start by defining our GraphQL schema. The schema is a blueprint for the structure of your API.

Below is the schema we will use:

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

class QueryType < GraphQL::Schema::Object
  field :books, [BookType], null: false do
    argument :genre, String, required: false
    argument :author, String, required: false
  end

  def books(genre: nil, author: nil)
    # Resolver implementation will be shown in the next section
  end
end

class MutationType < GraphQL::Schema::Object
  field :add_book, BookType, null: false do
    argument :title, String, required: true
    argument :author, String, required: true
    argument :published_date, String, required: false
    argument :genre, String, required: false
  end

  def add_book(title:, author:, published_date: nil, genre: nil)
    # Resolver implementation will be shown later
  end
end

In this schema:

  • BookType defines the structure of a book object.
  • QueryType has a books field that accepts two optional arguments, genre and author, to filter books.
  • MutationType has an add_book field that accepts arguments to add a new book to our dataset.

Resolvers: Filtering Data with Query Arguments

Resolvers fetch the data specified in the schema. Here, we will write resolvers to handle the books query with filtering capabilities:

Ruby
class QueryType < GraphQL::Schema::Object
  field :books, [BookType], null: false do
    argument :genre, String, required: false
    argument :author, String, required: false
  end

  def books(genre: nil, author: nil)
    filtered_books = BOOKS.select do |book|
      (genre.nil? || book[:genre] == genre) &&
      (author.nil? || book[:author] == author)
    end
    filtered_books
  end
end

In this resolver:

  • The books query accepts genre and author as optional arguments.
  • It filters the BOOKS array based on these arguments using Ruby's select method.
  • If an argument is provided, it filters by that argument; otherwise, it includes all books.

Querying with Filter

For example, querying for books by a specific author:

GraphQL
query {
  books(author: "J.R.R. Tolkien") {
    title
    author
  }
}

This would return:

JSON
{
  "data": {
    "books": [
      {
        "title": "The Hobbit",
        "author": "J.R.R. Tolkien"
      }
    ]
  }
}

Mutations: Adding New Entries with Arguments

Next, we handle mutations to add new entries. Here's how to set up the resolver for adding a book:

Ruby
require 'securerandom'

class MutationType < GraphQL::Schema::Object
  field :add_book, BookType, null: false do
    argument :title, String, required: true
    argument :author, String, required: true
    argument :published_date, String, required: false
    argument :genre, String, required: false
  end

  def add_book(title:, author:, published_date: nil, genre: nil)
    new_book = {
      id: SecureRandom.uuid,
      title: title,
      author: author,
      published_date: published_date,
      genre: genre
    }
    BOOKS << new_book
    new_book
  end
end

Attention: In this example, we store books in an in-memory BOOKS array. This is fine for learning purposes, but be aware that all data will be lost when the server restarts. In a production application, you would persist data to a database like PostgreSQL or MongoDB.

This resolver:

  • Accepts title, author, published_date, and genre as arguments.
  • Creates a new book hash with a unique id using SecureRandom.uuid.
  • Adds the new book to the BOOKS array.
  • Returns the newly added book.

Why SecureRandom.uuid?

SecureRandom.uuid is a Ruby standard library method that generates a universally unique identifier (UUID)—a 128-bit string like "550e8400-e29b-41d4-a716-446655440000". We use UUIDs instead of simple incrementing IDs (1, 2, 3...) for several reasons:

  • Uniqueness without coordination: UUIDs are statistically guaranteed to be unique without needing to check existing IDs or maintain a counter.
  • Security: Sequential IDs can expose information about your system (e.g., how many records exist) and make it easier to guess valid IDs.
  • Distributed systems: In real applications with multiple servers, incrementing IDs can cause collisions. UUIDs avoid this problem.

Field Name Conventions

Notice that our schema defines published_date using Ruby's snake_case convention, but in GraphQL queries we use publishedDate in camelCase. The graphql-ruby gem automatically converts between these conventions—snake_case in Ruby code becomes camelCase in the GraphQL API. This follows the idiomatic naming conventions of each language.

Example mutation request:

GraphQL
mutation {
  addBook(title: "1984", author: "George Orwell", publishedDate: "1949", genre: "Dystopian") {
    id
    title
    author
    publishedDate
    genre
  }
}

Response:

JSON
{
  "data": {
    "addBook": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "title": "1984",
      "author": "George Orwell",
      "publishedDate": "1949",
      "genre": "Dystopian"
    }
  }
}

Fetching Data Using Queries and Mutations in Ruby: Fetching Books

Finally, let's see how to fetch data using the Net::HTTP library in Ruby. We'll start by querying the list of books and then add a new book.

Ruby
require 'net/http'
require 'json'
require 'uri'

def fetch_books
  query = <<~GRAPHQL
    query {
      books {
        title
        author
        publishedDate
        genre
      }
    }
  GRAPHQL

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

  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 "Books: #{JSON.pretty_generate(data)}"
  rescue StandardError => e
    puts "Error: #{e.message}"
  end
end

fetch_books

Adding Data Using Queries and Mutations in Ruby: Adding a Book

Then, let's continue by trying to add a book using the proper mutation:

Ruby
def add_book(title, author, published_date, genre)
  mutation = <<~GRAPHQL
    mutation {
      addBook(title: "#{title}", author: "#{author}", publishedDate: "#{published_date}", genre: "#{genre}") {
        id
        title
        author
        publishedDate
        genre
      }
    }
  GRAPHQL

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

  begin
    http = Net::HTTP.new(url.host, url.port)
    request = Net::HTTP::Post.new(url.path, 'Content-Type' => 'application/json')
    request.body = { query: mutation }.to_json

    response = http.request(request)
    data = JSON.parse(response.body)
    puts "Added Book: #{JSON.pretty_generate(data)}"
  rescue StandardError => e
    puts "Error: #{e.message}"
  end
end

add_book('1984', 'George Orwell', '1949', 'Dystopian')

Both examples demonstrate how to send queries and mutations to the GraphQL server and handle the responses.

Summary and Next Steps

In this lesson, we covered how to enhance your GraphQL API by using advanced arguments in queries and mutations. You learned how to:

  • Define a GraphQL schema with advanced arguments using graphql-ruby.
  • Implement resolvers to handle these arguments.
  • Perform queries and mutations via Net::HTTP in Ruby.

This knowledge allows you to create more flexible and powerful GraphQL APIs. Now, it's time for you to practice these concepts with the exercises that follow, which will help you solidify your understanding and build confidence in using advanced GraphQL features.

Good luck, and happy coding!

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