Consumindo GraphQL com Elixir?
Tags: elixir, erlang, graphql, programming
Published: 2022-06-02 16:41:55

Hoje em dia é muito comum ver um serviço backend em [Elixir](https://elixir-lang.org) servindo uma API com endpoint [GraphQL](https://graphql.org) usando uma biblioteca chamada [Absinthe](https://github.com/absinthe-graphql/absinthe).

Mas você já se perguntou como funcionaria isso se a nossa necessidade fosse consumir um _Endpoint GraphQL_ já existente em outro serviço externo?

Digamos que você precise ingerir e processar dados para renderizar isso no seu frontend com Elixir (usando LiveView, etc).

A ideia deste texto é mostrar o quão simples é consumir dados de um endpoint GraphQL usando apenas requests [HTTP](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol).

Vamos la!

![lets go gif](https://media.giphy.com/media/cdNSp4L5vCU7aQrYnV/giphy.gif)


## O que é GraphQL?
Grosseiramente falando, é uma linguagem de busca/manipulação de dados e também uma ferramenta para criação de APIs.

Uma implementação de GraphQL te permite ter os dois lados da moeda:
1. Criar um serviço backend de [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) _(Create, Read, Update e Delete)_ para recursos.
2. Consumir dados deste serviço a partir de um frontend com requisições _HTTP_ ou utilizando uma biblioteca de cliente apropriado para o mesmo.

E diferente de uma implementação [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) padrão, onde você teria um endpoint para cada ação do seu CRUD/serviço. Aqui você irá utilizar um único endpoint, e através do _payload_ _(corpo da requisição)_ você indicará o que você precisa, seja busca ou manipulação de dados, e irá elencar quais dados de um determinado modelo.

Como isso funciona na prática? Usando como exemplo o endpoint disponibilizado pelo **GitHub**, irei requisitar dados do repositório oficial do [Elixir](https://github.com/elixir-lang/elixir).

Para ter uma noção mais apurada sobre como funciona o endpoint do github, recomendo este [link](https://docs.github.com/en/graphql/overview/about-the-graphql-api).

Irei usar a seguinte Query para buscar informações básicas sobre o Repo:
```
query {
  repository(owner: "elixir-lang", name: "elixir") {
    name,
    pushedAt,
    createdAt,
    owner { login },
    latestRelease { id },
    defaultBranchRef { name }
  }
}
```

Note que eu determino o primeiro termo _query_ para indicar que estou buscando dados, e logo após, _repository_ para indicar que os dados que estou buscando são sobre repositórios cujo owner se chama `elixir-lang` e o nome seja `elixir`. 
Como resposta a isso, receberei os mesmos dados `name, pushedAt, createdAt, owner.login, latestRelease.id, defaultBranchRef.name` preenchidos.

Vale ressaltar que no graphql eu determino exatamente o que eu estou buscando, e a resposta será praticamente espelhada nessa requisição.

Para simular essa query estarei usando o cURL, mas caso você queira testar de outra maneira, existe um cliente próprio para isso chamado [GraphiQL](https://github.com/graphql/graphiql).

cURL:
```
curl -X POST "https://api.github.com/graphql" \
-H "Authorization: Bearer SEU_TOKEN_AQUI" \
-d '{"query": "query { repository(owner: \"elixir-lang\", name: \"elixir\") { name, pushedAt, createdAt, owner{login}, latestRelease{id}, defaultBranchRef{name} } }"}'
```
(Substitua a parte "SEU_TOKEN_AQUI", pelo seu [token pessoal](https://github.com/settings/tokens) para testes)

Obtemos a seguinte resposta:
```
{
  "data": {
    "repository": {
      "name": "elixir",
      "pushedAt": "2022-06-01T15:57:31Z",
      "createdAt": "2011-01-09T08:43:57Z",
      "owner": {
        "login":  "elixir-lang"
      },
      "latestRelease": {
        "id":"RE_kwDOABLXGs4DzeaA"
      },
      "defaultBranchRef": {
        "name":"main"
      }
    }
  }
}
```
A resposta irá conter exatamente o que pedimos na requisição.

Resumo: Com apenas um endpoint e um payload flexível, eu consigo dizer para o serviço backend oque eu preciso, e ele me responde somente o necessário.

Para mais informações sobre GraphQL, eu recomendo este [link](https://graphql.org/learn/).


## Como consumir um Endpoint com Elixir?
Como vimos anteriormente, é possível consumir um endpoint apenas fazendo chamadas HTTP para o mesmo. 

Seguindo esta ideia, vou simular a requisição acima com Elixir, utilizando as seguintes bibliotecas:

- [httpoison:1.8](https://github.com/edgurgel/httpoison) (Cliente HTTP, para requisições)
- [jason:1.3](https://github.com/michalmuskala/jason) (Conversor de JSON)

```elixir
defmodule Github.GraphQL do
  @moduledoc """
  Módulo responsável por se comunicar com o endpoint GraphQL do Github.
  """

  @endpoint ~s(https://api.github.com/graphql)
  @token ~s(SEU_TOKEN_AQUI)

  @doc """
  Busca informações básicas de um determinado repositório.
  
  ## Exemplos
  
      iex> repo_info("elixir-lang", "elixir")
      %{
        "data" => %{
          "repository" => %{
            "createdAt" => "2011-01-09T08:43:57Z",
            "defaultBranchRef" => %{"name" => "main"},
            "latestRelease" => %{"id" => "RE_kwDOABLXGs4DzeaA"},
            "name" => "elixir",
            "owner" => %{"login" => "elixir-lang"},
            "pushedAt" => "2022-06-01T15:57:31Z"
          }
        }
      }
  """
  @spec repo_info(String.t(), String.t()) :: map()
  def repo_info(owner, repo) do
    payload = %{
      variables: %{owner: owner, name: repo},
      query: ~S"""
      query GetRepoInfo($owner: String!, $name: String!) {
        repository(owner: $owner, name: $name){
          name,
          pushedAt,
          createdAt,
          owner {
            login
          },
          latestRelease {
            id
          },
          defaultBranchRef {
            name
          }
        }
      }
      """
    } |> Jason.encode!()

    with {:ok, %HTTPoison.Response{body: body, status_code: 200}} <- HTTPoison.post(@endpoint, payload, headers()) do
      Jason.decode!(body)
    end
  end

  defp headers do
    [
      {"Authorization", "Bearer #{@token}"},
      {"Content-Type", "application/json"}
    ]
  end
end
```

Perceba como no payload eu criei um map com dois valores (`variables` e `query`). Neste caso, por motivos de organização, optei por isolar as variáveis `name` e `owner` do resto da query, para não precisar fazer interpolação na String. (Isto é algo próprio do GraphQL, onde o campo `variables` é opcional).

A execução seria:
```elixir
iex> repo_info("elixir-lang", "elixir")
%{
  "data" => %{
    "repository" => %{
      "createdAt" => "2011-01-09T08:43:57Z",
      "defaultBranchRef" => %{"name" => "main"},
      "latestRelease" => %{"id" => "RE_kwDOABLXGs4DzeaA"},
      "name" => "elixir",
      "owner" => %{"login" => "elixir-lang"},
      "pushedAt" => "2022-06-01T15:57:31Z"
    }
  }
}
```

Por padrão um endpoint GraphQL recebe as seguintes informações via payload:
```
{
  "query": "query a ser executada"
  "variables": {"variavel": "valor"}
  "operationName": "NomeDaOperação"
}
```

E a resposta é sempre a mesma, contendo uma chave `data` com os dados requisitados, ou uma chave `errors` contendo os possíveis problemas na construção da sua _query_.

## Conclusão

Consumir um endpoint GraphQL pode ser tão simples quanto consumir qualquer outro serviço utilizando o seu cliente HTTP de preferência.

E ainda é possível criar todo um padrão para consumo GraphQL com Elixir, usando behaviours e contextos para envelopar o código, pattern matching para tratamento de erros nas respostas, organização das variáveis de ambiente/aplicação, e por ai vai...

Isso te interessou? foi útil? Deixa nos comentários para eu ficar por dentro!

Abraços!

**💚💜 Elixir é amor, Erlang é vida! 💜💚**