Skip to content

TheRealReal/graphql_client

Repository files navigation

Tests Contributor Covenant

Graphql Client

A client-side GraphQL library.

Installation

Add graphql_client to you list of dependencies:

def deps do
  [{:graphql_client, "~> 0.1"}]
end

Creating a backend

Now, you need to implement the GraphQL.Client behaviour:

defmodule MyClient do
  @behaviour GraphQL.Client

  def execute_query(query, variables, options) do
    # your implementation
  end
end

Configuring the client

In your configuration, set it as your backend:

config :graphql_client, backend: MyClient

Now, any call to GraphQL.Client will use the configured backend.

Usage

GraphQL as code

To build queries, you can import all functions from GraphQL.QueryBuilder.

A simple query, like this one:

query User($slug: String! = "*") {
  user(slug: $slug) {
    id
    email
  }
}

Can be built using the following snippet:

import GraphQL.QueryBuilder

user_query = query("User", %{slug: {"String!", "*"}}, [
  field(:user, %{slug: :"$slug"}, [
    field(:id),
    field(:email)
  ])
])

Now, the user_query variable contains a representation of this GraphQL operation. If you inspect it, you'll see this:

%GraphQL.Query{
  fields: [
    %GraphQL.Node{
      alias: nil,
      arguments: %{slug: :"$slug"},
      name: :user,
      node_type: :field,
      nodes: [
        %GraphQL.Node{
          alias: nil,
          arguments: nil,
          name: :id,
          node_type: :field,
          nodes: nil,
          type: nil
        },
        %GraphQL.Node{
          alias: nil,
          arguments: nil,
          name: :email,
          node_type: :field,
          nodes: nil,
          type: nil
        }
      ],
      type: nil
    }
  ],
  fragments: [],
  name: "User",
  operation: :query,
  variables: [
    %GraphQL.Variable{
      default_value: "*",
      name: :slug,
      type: "String!"
    }
  ]
}

But most of the time you'll not need to handle this directly.

Executing queries

To execute this query, you can now call the GraphQL.Client and use this query directly:

GraphQL.Client.execute(user_query, %{slug: "some-user"})

From the POV of the code that it's calling, it doesn't know if this client is using HTTP, smoke signals or magic.

All you know is that this function will always return a %GraphQL.Response{} struct.

To get the actual text body, you can use GraphQL.Encoder.encode/1 function:

iex> user_query |> GraphQL.Encoder.encode() |> IO.puts()
query User($slug: String! = "*") {
  user(slug: $slug) {
    id
    email
  }
}
:ok

The Query Registry

The end goal is to merge different queries into one operation and the query registry does exactly that.

It will accumulate queries, variables and resolvers (yes, resolvers!), merge them, and then execute resolvers with an accumulator.

user_query = query(...)
product_query = query(...)

user_resolver = fn response, acc ->
  # do something with the response and return the updated accumulator
  updated_acc
end

registry = QueryRegistry.new("BigQuery")

result =
  registry
  |> QueryRegistry.add_query(user_query, user_variables,[user_resolver])
  |> QueryRegistry.add_query(product_query, product_variables)
  |> QueryRegistry.execute(%{}, options)

A resolver function must accept two parameters: a %GraphQL.Response{} struct and the accumulator defined by the query registry.

Testing

The %GraphQL.Response{} is the only thing clients must return, and that we can configure the backend via config files.

Internally, during tests, the backend will be changed to LocalBackend, that uses an Agent process to store responses.

Call GraphQL.LocalBackend.start_link/0 on your test_helper.exs file.

Now you can use the GraphQL.LocalBackend.expect/1 function:

import GraphQL.LocalBackend, only: [expect: 1]
alias GraphQL.Response

test "my test" do
  my_registry = QueryRegistry.new(...)
  response = Response.success(%{field: "value"})
  expect(my_registry, response)
  assert 1 == 1
end

If you need to inspect and assert the query and variables, you can pass a function:

import GraphQL.LocalBackend, only: [expect: 1]
alias GraphQL.Response

test "my test" do
  my_registry = QueryRegistry.new(...)

  expect(my_registry, fn query, _variables, _options ->
    assert query == expected_query
    Response.success(%{field: "value"})
  end)
  assert 1 == 1
end

Code of Conduct

This project uses Contributor Covenant version 2.1. Check CODE_OF_CONDUCT.md file for more information.

License

graphql_client source code is released under Apache License 2.0.

Check NOTICE and LICENSE files for more information.

Releases

No releases published

Packages

No packages published

Languages