{"id":13879733,"url":"https://github.com/ashkan18/graphlient","last_synced_at":"2025-05-14T06:12:31.697Z","repository":{"id":39620293,"uuid":"105709496","full_name":"ashkan18/graphlient","owner":"ashkan18","description":"Ruby GraphQL Client","archived":false,"fork":false,"pushed_at":"2024-11-10T14:21:05.000Z","size":377,"stargazers_count":255,"open_issues_count":20,"forks_count":45,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-07T16:06:20.319Z","etag":null,"topics":["client","graphql","ruby"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ashkan18.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-03T22:19:42.000Z","updated_at":"2025-04-04T13:53:45.000Z","dependencies_parsed_at":"2024-01-13T20:57:43.278Z","dependency_job_id":"2412ea90-ea1f-4615-a58c-aa684aba1a66","html_url":"https://github.com/ashkan18/graphlient","commit_stats":{"total_commits":130,"total_committers":22,"mean_commits":5.909090909090909,"dds":0.6076923076923078,"last_synced_commit":"025f303bfa1cd19499ae21041b885937d15be10b"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ashkan18%2Fgraphlient","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ashkan18%2Fgraphlient/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ashkan18%2Fgraphlient/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ashkan18%2Fgraphlient/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ashkan18","download_url":"https://codeload.github.com/ashkan18/graphlient/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247918919,"owners_count":21018044,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","graphql","ruby"],"created_at":"2024-08-06T08:02:30.770Z","updated_at":"2025-04-10T23:25:19.957Z","avatar_url":"https://github.com/ashkan18.png","language":"Ruby","readme":"# Graphlient\n\n[![Gem Version](https://badge.fury.io/rb/graphlient.svg)](https://badge.fury.io/rb/graphlient)\n[![Build Status](https://github.com/ashkan18/graphlient/actions/workflows/ci.yml/badge.svg)](https://github.com/ashkan18/graphlient/actions/workflows/ci.yml)\n\nA friendlier Ruby client for consuming GraphQL-based APIs. Built on top of your usual [graphql-client](https://github.com/github-community-projects/graphql-client), but with better defaults, more consistent error handling, and using the [faraday](https://github.com/lostisland/faraday) HTTP client.\n\n# Table of Contents\n\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Schema Storing and Loading on Disk](#schema-storing-and-loading-on-disk)\n  - [Preloading Schema Once](#preloading-schema-once)\n  - [Error Handling](#error-handling)\n  - [Executing Parameterized Queries and Mutations](#executing-parameterized-queries-and-mutations)\n  - [Parse and Execute Queries Separately](#parse-and-execute-queries-separately)\n  - [Dynamic vs. Static Queries](#dynamic-vs-static-queries)\n  - [Generate Queries with Graphlient::Query](#generate-queries-with-graphlientquery)\n  - [Create API Client Classes with Graphlient::Extension::Query](#create-api-client-classes-with-graphlientextensionquery)\n  - [Swapping the HTTP Stack](#swapping-the-http-stack)\n  - [Testing with Graphlient and RSpec](#testing-with-graphlient-and-rspec)\n- [License](#license)\n\n## Installation\n\nAdd the following line to your Gemfile.\n\n```ruby\ngem 'graphlient'\n```\n\n## Usage\n\nCreate a new instance of `Graphlient::Client` with a URL and optional headers/http_options.\n\n```ruby\nclient = Graphlient::Client.new('https://test-graphql.biz/graphql',\n  headers: {\n    'Authorization' =\u003e 'Bearer 123'\n  },\n  http_options: {\n    read_timeout: 20,\n    write_timeout: 30\n  }\n)\n```\n\n| http_options  | default | type    |\n| ------------- | ------- | ------- |\n| read_timeout  | nil     | seconds |\n| write_timeout | nil     | seconds |\n\nThe schema is available automatically via `.schema`.\n\n```ruby\nclient.schema # GraphQL::Schema\n```\n\nMake queries with `query`, which takes a String or a block for the query definition.\n\nWith a String.\n\n```ruby\nresponse = client.query \u003c\u003c~GRAPHQL\n  query {\n    invoice(id: 10) {\n      id\n      total\n      line_items {\n        price\n        item_type\n      }\n    }\n  }\nGRAPHQL\n```\n\nWith a block.\n\n```ruby\nresponse = client.query do\n  query do\n    invoice(id: 10) do\n      id\n      total\n      line_items do\n        price\n        item_type\n      end\n    end\n  end\nend\n```\n\nThis will call the endpoint setup in the configuration with `POST`, the `Authorization` header and `query` as follows.\n\n```graphql\nquery {\n  invoice(id: 10) {\n    id\n    total\n    line_items {\n      price\n      item_type\n    }\n  }\n}\n```\n\nA successful response object always contains data which can be iterated upon. The following example returns the first line item's price.\n\n```ruby\nresponse.data.invoice.line_items.first.price\n```\n\nYou can also execute mutations the same way.\n\n```ruby\nresponse = client.query do\n  mutation do\n    createInvoice(input: { fee_in_cents: 12_345 }) do\n      id\n      fee_in_cents\n    end\n  end\nend\n```\n\nThe successful response contains data in `response.data`. The following example returns the newly created invoice's ID.\n\n```ruby\nresponse.data.create_invoice.first.id\n```\n\n### Schema storing and loading on disk\n\nTo reduce requests to graphql API you can cache schema:\n\n```ruby\nclient = Client.new(url, schema_path: 'config/your_graphql_schema.json')\nclient.schema.dump! # you only need to call this when graphql schema changes\n```\n\n### Preloading Schema Once\n\nEven if caching the schema on disk, instantiating `Graphlient::Client` often can be both time and memory intensive due to loading the schema for each instance. This is especially true if the schema is a large file. To get around these performance issues, instantiate your schema once and pass it in as a configuration option.\n\nOne time in an initializer\n\n```ruby\nschema = Graphlient::Schema.new(\n  'https://graphql.foo.com/graphql', 'lib/graphql_schema_foo.json'\n)\n```\n\nPass in each time you initialize a client\n\n```\nclient = Graphlient::Client.new(\n  'https://graphql.foo.com/graphql',\n  schema: schema,\n  headers: {\n    'Authorization' =\u003e 'Bearer 123',\n  }\n)\n```\n\n### Error Handling\n\nUnlike graphql-client, Graphlient will always raise an exception unless the query has succeeded.\n\n* [Graphlient::Errors::ClientError](lib/graphlient/errors/client_error.rb): all client-side query validation failures based on current schema\n* [Graphlient::Errors::GraphQLError](lib/graphlient/errors/graphql_error.rb): all GraphQL API errors, with a humanly readable collection of problems\n* [Graphlient::Errors::ExecutionError](lib/graphlient/errors/execution_error.rb): all GraphQL execution errors, with a humanly readable collection of problems\n* [Graphlient::Errors::ServerError](lib/graphlient/errors/server_error.rb): all transport errors raised by HTTP Adapters. You can access `inner_exception`, `status_code` and `response` on these errors to get more details on what went wrong\n* [Graphlient::Errors::FaradayServerError](lib/graphlient/errors/faraday_server_error.rb): this inherits from `ServerError` ☝️, we recommend using `ServerError` to rescue these\n* [Graphlient::Errors::HttpServerError](lib/graphlient/errors/http_server_error.rb): this inherits from `ServerError` ☝️, we recommend using `ServerError` to rescue these\n* [Graphlient::Errors::ConnectionFailedError](lib/graphlient/errors/connection_failed_error.rb): this inherits from `ServerError` ☝️, we recommend using `ServerError` to rescue these\n* [Graphlient::Errors::TimeoutError](lib/graphlient/errors/timeout_error.rb): this inherits from `ServerError` ☝️, we recommend using `ServerError` to rescue these\n* [Graphlient::Errors::HttpOptionsError](lib/graphlient/errors/http_options_error.rb): all NoMethodError raised by HTTP Adapters when given options in `http_options` are invalid\n\n\nAll errors inherit from `Graphlient::Errors::Error` if you need to handle them in bulk.\n\n### Executing Parameterized Queries and Mutations\n\nGraphlient can execute parameterized queries and mutations by providing variables as query parameters.\n\nThe following query accepts an array of IDs.\n\nWith a String.\n\n```ruby\nquery = \u003c\u003c-GRAPHQL\n  query($ids: [Int]) {\n    invoices(ids: $ids) {\n      id\n      fee_in_cents\n    }\n  }\nGRAPHQL\nvariables = { ids: [42] }\n\nclient.query(query, variables)\n```\n\nWith a block.\n\n```ruby\nclient.query(ids: [42]) do\n  query(ids: [:int]) do\n    invoices(ids: :ids) do\n      id\n      fee_in_cents\n    end\n  end\nend\n```\n\nGraphlient supports following Scalar types for parameterized queries by default:\n\n- `:id` maps to `ID`\n- `:boolean` maps to `Boolean`\n- `:float` maps to `Float`\n- `:int` maps to `Int`\n- `:string` maps to `String`\n\nYou can use any of the above types with `!` to make it required or use them in `[]` for array parameters.\n\nFor any other custom types, graphlient will simply use `to_s` of the symbol provided for the type, so `query(ids: [:InvoiceType!])` will result in `query($ids: [InvoiceType!])`.\n\nThe following mutation accepts a custom type that requires `fee_in_cents`.\n\n```ruby\nclient.query(input: { fee_in_cents: 12_345 }) do\n  mutation(input: :createInvoiceInput!) do\n    createInvoice(input: :input) do\n      id\n      fee_in_cents\n    end\n  end\nend\n```\n\n### Parse and Execute Queries Separately\n\nYou can `parse` and `execute` queries separately with optional variables. This is highly recommended as parsing a query and validating a query on every request adds performance overhead. Parsing queries early allows validation errors to be discovered before request time and avoids many potential security issues.\n\n```ruby\n# parse a query, returns a GraphQL::Client::OperationDefinition\nquery = client.parse do\n  query(ids: [:int]) do\n    invoices(ids: :ids) do\n      id\n      fee_in_cents\n    end\n  end\nend\n\n# execute a query, returns a GraphQL::Client::Response\nclient.execute query, ids: [42]\n```\n\nOr pass in a string instead of a block:\n\n```ruby\n# parse a query, returns a GraphQL::Client::OperationDefinition\nquery = client.parse \u003c\u003c~GRAPHQL\n  query($some_id: Int) {\n    invoice(id: $some_id) {\n      id\n      feeInCents\n    }\n  }\nGRAPHQL\n\n# execute a query, returns a GraphQL::Client::Response\nclient.execute query, ids: [42]\n```\n\n### Dynamic vs. Static Queries\n\nGraphlient uses [graphql-client](https://github.com/github-community-projects/graphql-client), which [recommends](https://github.com/github-community-projects/graphql-client/blob/master/guides/dynamic-query-error.md) building queries as static module members along with dynamic variables during execution. This can be accomplished with graphlient the same way.\n\nCreate a new instance of `Graphlient::Client` with a URL and optional headers.\n\n```ruby\nmodule SWAPI\n  Client = Graphlient::Client.new('https://test-graphql.biz/graphql',\n    headers: {\n      'Authorization' =\u003e 'Bearer 123'\n    },\n    allow_dynamic_queries: false\n  )\nend\n```\n\nThe schema is available automatically via `.schema`.\n\n```ruby\nSWAPI::Client.schema # GraphQL::Schema\n```\n\nDefine a query.\n\n```ruby\nmodule SWAPI\n  InvoiceQuery = Client.parse do\n    query(id: :int) do\n      invoice(id: :id) do\n        id\n        fee_in_cents\n      end\n    end\n  end\nend\n```\n\nExecute the query.\n\n```ruby\nresponse = SWAPI::Client.execute(SWAPI::InvoiceQuery, id: 42)\n```\n\nNote that in the example above the client is created with `allow_dynamic_queries: false` (only allow static queries), while graphlient defaults to `allow_dynamic_queries: true` (allow dynamic queries). This option is marked deprecated, but we're proposing to remove it and default it to `true` in [graphql-client#128](https://github.com/github-community-projects/graphql-client/issues/128).\n\n### Generate Queries with Graphlient::Query\n\nYou can directly use `Graphlient::Query` to generate raw GraphQL queries.\n\n```ruby\nquery = Graphlient::Query.new do\n  query do\n    invoice(id: 10) do\n      line_items\n    end\n  end\nend\n\nquery.to_s\n# \"\\nquery {\\n  invoice(id: 10){\\n    line_items\\n    }\\n  }\\n\"\n```\n\n### Use of Fragments\n\n[Fragments](https://github.com/github-community-projects/graphql-client#defining-queries) should be referred by constant:\n\n```ruby\nmodule Fragments\n  Invoice = client.parse \u003c\u003c~'GRAPHQL'\n    fragment on Invoice {\n      id\n      feeInCents\n    }\n  GRAPHQL\nend\n```\n\n`Graphlient` offers the syntax below to refer to the original constant:\n  * Triple underscore `___` to refer to the fragment\n  * Double underscore `__` for namespace separator\n\nIn this example, `Fragments::Invoice` would be referred as follows:\n\n```ruby\ninvoice_query = client.parse do\n  query do\n    invoice(id: 10) do\n      id\n      ___Fragments__Invoice\n    end\n  end\nend\n```\n\nThe wrapped response only allows access to fields that have been explicitly asked for.\nIn this example, while `id` has been referenced directly in the main query, `feeInCents` has been spread via fragment and trying to access it in the original wrapped response will throw [`GraphQL::Client::ImplicitlyFetchedFieldError`](https://github.com/github-community-projects/graphql-client/blob/master/guides/implicitly-fetched-field-error.md) (to prevent data leaks between components).\n\n```ruby\nresponse = client.execute(invoice_query)\nresult = response.data.invoice\nresult.to_h\n# {\"id\" =\u003e 10, \"feeInCents\"=\u003e 20000}\nresult.id\n# 10\nresult.fee_in_cents\n# raises GraphQL::Client::ImplicitlyFetchedFieldError\n```\n\n`feeInCents` cannot be fetched directly from the main query, but from the fragment as shown below:\n\n```ruby\ninvoice = Fragments::Invoice.new(result)\ninvoice.id\n# 10\ninvoice.fee_in_cents\n# 20000\n```\n\n### Create API Client Classes with Graphlient::Extension::Query\n\nYou can include `Graphlient::Extensions::Query` in your class. This will add a new `method_missing` method to your context which will be used to generate GraphQL queries.\n\n```ruby\ninclude Graphlient::Extensions::Query\n\nquery = query do\n  invoice(id: 10) do\n    line_items\n  end\nend\n\nquery.to_s\n# \"\\nquery{\\n  invoice(id: 10){\\n    line_items\\n    }\\n  }\\n\"\n```\n\n### Swapping the HTTP Stack\n\nYou can swap the default Faraday adapter for `Net::HTTP`.\n\n```ruby\nclient = Graphlient::Client.new('https://test-graphql.biz/graphql',\n  http: Graphlient::Adapters::HTTP::HTTPAdapter\n)\n```\n\n### Testing with Graphlient and RSpec\n\nUse Graphlient inside your RSpec tests in a Rails application or with `Rack::Test` against your actual application.\n\n```ruby\nrequire 'spec_helper'\n\ndescribe App do\n  include Rack::Test::Methods\n\n  def app\n    # ...\n  end\n\n  let(:client) do\n    Graphlient::Client.new('http://test-graphql.biz/graphql') do |client|\n      client.http do |h|\n        h.connection do |c|\n          c.adapter Faraday::Adapter::Rack, app\n        end\n      end\n    end\n  end\n\n  context 'an invoice' do\n    let(:result) do\n      client.query do\n        query do\n          invoice(id: 10) do\n            id\n          end\n        end\n      end\n    end\n\n    it 'can be retrieved' do\n      expect(result.data.invoice.id).to eq 10\n    end\n  end\nend\n```\n\nAlternately you can `stub_request` with Webmock.\n\n```ruby\ndescribe App do\n  let(:url) { 'http://example.com/graphql' }\n  let(:client) { Graphlient::Client.new(url) }\n\n  before do\n    stub_request(:post, url).to_return(\n      status: 200,\n      body: DummySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY).to_json\n    )\n  end\n\n  it 'retrieves schema' do\n    expect(client.schema).to be_a Graphlient::Schema\n  end\nend\n```\n\nIn order to stub the response to actual queries, [dump the schema into a JSON file](#schema-storing-and-loading-on-disk) and specify it via schema_path as follows.\n\n```ruby\ndescribe App do\n  let(:url) { 'http://graph.biz/graphql' }\n  let(:client) { Graphlient::Client.new(url, schema_path: 'spec/support/fixtures/invoice_api.json') }\n  let(:query) do\n    \u003c\u003c~GRAPHQL\n      query{\n        invoice(id: 42) {\n          id\n          feeInCents\n        }\n      }\n    GRAPHQL\n  end\n  let(:json_response) do\n    {\n      'data' =\u003e {\n        'invoice' =\u003e {\n          'id' =\u003e '42',\n          'feeInCents' =\u003e 2000\n        }\n      }\n    }.to_json\n  end\n\n  before do\n    stub_request(:post, url).to_return(\n      status: 200,\n      body: json_response\n    )\n  end\n\n  it 'returns invoice fees' do\n    response = client.query(query)\n    expect(response.data).to be_truthy\n    expect(response.data.invoice.id).to eq('42')\n    expect(response.data.invoice.fee_in_cents).to eq(2000)\n  end\nend\n```\n\n## License\n\nMIT License, see [LICENSE](LICENSE)\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fashkan18%2Fgraphlient","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fashkan18%2Fgraphlient","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fashkan18%2Fgraphlient/lists"}