A client-side GraphQL library.
Add graphql_client to you list of dependencies:
def deps do
[{:graphql_client, "~> 0.1"}]
endCreating 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
endConfiguring the client
In your configuration, set it as your backend:
config :graphql_client, backend: MyClientNow, any call to GraphQL.Client will use the configured backend.
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.
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 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.
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
endIf 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
endThis project uses Contributor Covenant version 2.1. Check CODE_OF_CONDUCT.md file for more information.
graphql_client source code is released under Apache License 2.0.