Graphlient Ruby Client

repository·master·Indexed 42 days ago

https://github.com/ashkan18/graphlient

A developer-friendly Ruby client for GraphQL APIs built on top of graphql-client. It provides improved defaults, consistent error handling, and integrates with the Faraday HTTP library. Features include a DSL for defining queries and mutations, schema caching to disk, support for parameterized queries, custom scalar registration, and the ability to separate query parsing from execution.

Tokens
6.5K
Snippets
26
Records
34
Agent score
54%

What's inside Graphlient

  1. Overview of Graphlient

    master

    Graphlient is a Ruby client designed for consuming GraphQL-based APIs. It is built on top of the graphql-client gem but provides several improvements:

    • Better default configurations.
    • More consistent error handling.
    • Uses the faraday HTTP client by default.
  2. Define and use fragments in the DSL

    master

    Graphlient supports two ways to use fragments:

    1. Inline Fragments: Define the fragment body using fragment(:Name, on: :Type) within the same query block. These fields are directly accessible on the response.
    2. External Fragment Constants: Refer to fragments defined as constants using the ___ (triple underscore) prefix for the fragment and __ (double underscore) for namespace separators. These follow graphql-client component isolation, meaning fields are only accessible by wrapping the result in the fragment constant class.
    # 1. Inline Fragments
    response = client.query do
      fragment(:InvoiceFields, on: :Invoice) do
        id
      end
      query do
        invoice(id: 10) do
          spread :InvoiceFields
        end
      end
    end
    puts response.data.invoice.id # Accessible directly
    
    # 2. External Fragments
    module Fragments
      Invoice = client.parse("fragment on Invoice { id }")
    end
    
    query = client.parse do
      query do
        invoice(id: 10) do
          ___Fragments__Invoice
        end
      end
    end
    
    result = client.execute(query).data.invoice
    puts result.id # Accessible
    # result.other_field would raise GraphQL::Client::ImplicitlyFetchedFieldError
    
    # To access fields from the external fragment:
    invoice_obj = Fragments::Invoice.new(result)
    puts invoice_obj.id
  3. Optimize performance by preloading the schema

    master

    Instantiating a Graphlient::Client can be resource-intensive if it has to load a large schema file every time. To optimize this, instantiate a Graphlient::Schema once (e.g., in an initializer) and pass that instance into the schema: option when creating new clients.

    # In an initializer
    schema = Graphlient::Schema.new(
      'https://graphql.foo.com/graphql', 'lib/graphql_schema_foo.json'
    )
    
    # When creating clients
    client = Graphlient::Client.new(
      'https://graphql.foo.com/graphql',
      schema: schema,
      headers: { 'Authorization' => 'Bearer 123' }
    )
  4. Use static queries (graphql-client style)

    master

    For better performance and following graphql-client recommendations, build queries as static members. Set allow_dynamic_queries: false in the client configuration, parse the query once into a constant, and then execute that constant.

    module SWAPI
      Client = Graphlient::Client.new('https://test-graphql.biz/graphql', allow_dynamic_queries: false)
    
      InvoiceQuery = Client.parse do
        query(id: :int) do
          invoice(id: :id) do
            id
          end
        end
      end
    end
    
    # Execution
    response = SWAPI::Client.execute(SWAPI::InvoiceQuery, id: 42)
  5. Execute parameterized queries and mutations

    master

    Pass variables to queries or mutations by providing them as arguments to the query method. You can define types using symbols. Supported default scalars include :id, :boolean, :float, :int, and :string. Use ! for non-null types and [] for arrays.

    # Using a String with variables
    query = <<-GRAPHQL
      query($ids: [Int]) {
        invoices(ids: $ids) {
          id
        }
      }
    GRAPHQL
    variables = { ids: [42] }
    client.query(query, variables)
    
    # Using a block with variables
    client.query(ids: [42]) do
      query(ids: [:int]) do
        invoices(ids: :ids) do
          id
        end
      end
    end
  6. Upgrade to Graphlient >= 0.4.0

    master

    When upgrading to version 0.4.0 or higher, ensure your environment uses Faraday >= 1.0.

    Note a change in error handling: if a GraphQL request fails due to a server error, Graphlient now returns Graphlient::Errors::FaradayServerError instead of the previous Faraday::ServerError.

  7. Apply GraphQL directives in the DSL

    master

    Apply directives using the _name convention. Any method starting with an underscore is treated as a directive (e.g., _skip becomes @skip). Directives can be applied to fields, fragment spreads, and inline fragments.

    # On a field
    client.query(skip_fee: :boolean!) do
      query(skip_fee: :skip_fee!) do
        invoice(id: :id) do
          feeInCents _skip(if: :skip_fee)
        end
      end
    end
    
    # On a fragment spread
    spread :InvoiceFields, _skip(if: :skip_invoice)
    
    # On an inline fragment
    spread(_skip(if: :skip_drafts), on: :DraftInvoice) { draftId }
  8. Parse and execute queries separately

    master

    To reduce performance overhead and improve security, you can separate the parsing/validation step from the execution step. Use client.parse to return a GraphQL::Client::OperationDefinition, then pass that definition to client.execute along with your variables.

    # 1. Parse (returns GraphQL::Client::OperationDefinition)
    query_def = client.parse do
      query(ids: [:int]) do
        invoices(ids: :ids) do
          id
        end
      end
    end
    
    # 2. Execute (returns GraphQL::Client::Response)
    client.execute(query_def, ids: [42])
  9. Initialize a Graphlient::Client

    master

    Create a new instance of Graphlient::Client by providing a URL. You can optionally configure headers and http_options (such as read_timeout and write_timeout). The schema is automatically loaded and accessible via the .schema method.

    client = Graphlient::Client.new('https://test-graphql.biz/graphql',
      headers: {
        'Authorization' => 'Bearer 123'
      },
      http_options: {
        read_timeout: 20,
        write_timeout: 30
      }
    )
  10. Execute queries and mutations

    master

    You can execute GraphQL queries or mutations using the query method. This method accepts either a raw GraphQL String or a Ruby block that uses a DSL to define the operation. Successful responses return an object where data is accessible via method chaining on the .data attribute.

    # Using a String
    response = client.query <<~GRAPHQL
      query {
        invoice(id: 10) {
          id
          total
        }
      }
    GRAPHQL
    
    # Using a block
    response = client.query do
      query do
        invoice(id: 10) do
          id
          total
        end
      end
    end
    
    # Accessing data
    puts response.data.invoice.id
  11. Cache the GraphQL schema on disk

    master

    To avoid making repeated requests to the GraphQL API to fetch the schema, you can cache it to a local JSON file using the schema_path option. When the schema changes on the server, call .schema.dump! to update the local file.

    client = Graphlient::Client.new(url, schema_path: 'config/your_graphql_schema.json')
    client.schema.dump! # Call this when the remote schema changes