Grephql

Compile-time GraphQL client for Elixir. Parses and validates queries during compilation, generates typed Ecto embedded schemas for responses and variables, and executes queries at runtime via Req.

Features

Installation

Add grephql to your dependencies in mix.exs:

def deps do
  [
    {:grephql, "~> 0.1.0"}
  ]
end

Quick Start

1. Download your schema

Use the built-in Mix task to download your GraphQL schema via introspection:

mix grephql.download_schema \
  --endpoint https://api.example.com/graphql \
  --output priv/schemas/schema.json \
  --header "Authorization: Bearer token123"

This sends an introspection query, validates the response, and saves it as JSON.

2. Define a client module

defmodule MyApp.GitHub do
  use Grephql,
    otp_app: :my_app,
    source: "priv/schemas/github.json",
    endpoint: "https://api.github.com/graphql"

  defgql :get_user, ~GQL"""
  query GetUser($login: String!) {
    user(login: $login) {
      name
      bio
    }
  }
  """

  defgql :get_viewer, ~GQL"""
  query {
    viewer {
      login
      email
    }
  }
  """
end

defgql parses and validates the query at compile time, generates typed response/variable modules, and defines a function you can call at runtime.

3. Call the generated functions

# With variables — validates input before sending
case MyApp.GitHub.get_user(%{login: "octocat"}) do
  {:ok, result} ->
    result.data.user.name  #=> "The Octocat"

  {:error, %Ecto.Changeset{} = changeset} ->
    # Variable validation failed
    changeset.errors

  {:error, %Req.Response{} = response} ->
    # HTTP error
    response.status
end

# Without variables
{:ok, result} = MyApp.GitHub.get_viewer()
result.data.viewer.login

Configuration

Configuration is resolved in order (later wins): compile-time defaults -> runtime config -> execute/3 opts.

Compile-time (in use)

use Grephql,
  otp_app: :my_app,
  source: "priv/schemas/github.json",
  endpoint: "https://api.github.com/graphql",
  req_options: [receive_timeout: 30_000],
  scalars: %{"DateTime" => Grephql.Types.DateTime}

Runtime (application config)

# config/runtime.exs
config :my_app, MyApp.GitHub,
  endpoint: "https://api.github.com/graphql",
  req_options: [auth: {:bearer, System.fetch_env!("GITHUB_TOKEN")}]

Per-call

MyApp.GitHub.get_user(%{login: "octocat"},
  endpoint: "https://other.api.com/graphql",
  req_options: [receive_timeout: 60_000]
)

The ~GQL Sigil and Formatter

The ~GQL sigil marks GraphQL strings for automatic formatting by mix format. Plain strings still work with defgql~GQL is optional.

Add the formatter plugin to your .formatter.exs:

[
  plugins: [Grephql.Formatter],
  # ...
]

Or via dependency import:

[
  import_deps: [:grephql],
  # ...
]

Before / After

# Before
defgql :get_user, ~GQL"query GetUser($id: ID!) { user(id: $id) { name email posts { title } } }"

# After mix format
defgql :get_user, ~GQL"query GetUser($id: ID!) {
  user(id: $id) {
    name
    email
    posts {
      title
    }
  }
}"

Custom Scalars

Map GraphQL custom scalars to Ecto types via the :scalars option:

use Grephql,
  otp_app: :my_app,
  source: "schema.json",
  scalars: %{
    "DateTime" => Grephql.Types.DateTime,
    "JSON"     => :map
  }

Grephql.Types.DateTime is included for ISO 8601 DateTime strings. For other custom scalars, provide any module implementing the Ecto.Type behaviour.

Unions and Interfaces

Union and interface types are resolved at decode time using the __typename field:

defgql :search, ~GQL"""
query Search($q: String!) {
  search(query: $q) {
    ... on User { name }
    ... on Repository { fullName }
  }
}
"""
{:ok, result} = MyApp.GitHub.search(%{q: "elixir"})

Enum.each(result.data.search, fn
  %{__typename: :user} = user -> IO.puts(user.name)
  %{__typename: :repository} = repo -> IO.puts(repo.full_name)
end)

Testing

Use Req.Test to stub HTTP responses without any network calls:

# config/test.exs
config :my_app, MyApp.GitHub,
  req_options: [plug: {Req.Test, MyApp.GitHub}]
test "get_user returns user data" do
  Req.Test.stub(MyApp.GitHub, fn conn ->
    Req.Test.json(conn, %{
      "data" => %{"user" => %{"name" => "Alice", "bio" => "Elixirist"}}
    })
  end)

  assert {:ok, result} = MyApp.GitHub.get_user(%{login: "alice"})
  assert result.data.user.name == "Alice"
end

Mix Tasks

mix grephql.download_schema

Downloads a GraphQL schema via introspection and saves it as JSON.

mix grephql.download_schema --endpoint URL --output PATH [--header "Key: Value"]
Option Required Description
--endpoint / -e yes GraphQL endpoint URL
--output / -o yes File path to save the schema JSON
--header / -h no HTTP header in "Key: Value" format (repeatable)

use Grephql Options

Option Required Description
:otp_app yes OTP application for runtime config lookup
:source yes Path to introspection JSON (relative to caller file) or inline JSON string
:endpoint no Default GraphQL endpoint URL
:req_options no Default Req options (keyword list)
:scalars no Map of GraphQL scalar name to Ecto type (default: %{})

Requirements

License

See LICENSE for details.