DEV Community

Cover image for Consumindo GraphQL com Elixir?
Willian Frantz
Willian Frantz

Posted on

Consumindo GraphQL com Elixir?

Hoje em dia é muito comum ver um serviço backend em Elixir servindo uma API com endpoint GraphQL usando uma biblioteca chamada 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.

Vamos la!

lets go 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 (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 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.

Para ter uma noção mais apurada sobre como funciona o endpoint do github, recomendo este link.

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 }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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} } }"}'
Enter fullscreen mode Exit fullscreen mode

(Substitua a parte "SEU_TOKEN_AQUI", pelo seu token pessoal 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"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

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"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Por padrão um endpoint GraphQL recebe as seguintes informações via payload:

{
  "query": "query a ser executada"
  "variables": {"variavel": "valor"}
  "operationName": "NomeDaOperação"
}
Enter fullscreen mode Exit fullscreen mode

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! 💜💚

Oldest comments (7)

Collapse
 
garaujodev profile image
Gustavo Araújo

Muito bom Willian! Estou estudando GraphQL e pretendo usar em alguns projetos Elixir, isso com certeza já clareou e muito minha mente!

Collapse
 
mensonones profile image
Emerson Vieira

Maravilhoso! Bem didático :D

Collapse
 
cyytrus profile image
Paulo Castro

Uma das melhores explciações que ja vi de como utilizar GraphQL com Elixir, parabéns cara, de verdade isso ficou incrível e o exemplo foi o melhor possível!

Collapse
 
elixir_utfpr profile image
Elixir UTFPR (por Adolfo Neto) • Edited

Parei no curl.

Recebi:

{
  "message": "Problems parsing JSON",
  "documentation_url": "https://docs.github.com/graphql"
}
Enter fullscreen mode Exit fullscreen mode

Sabe o que houve?

Collapse
 
elixir_utfpr profile image
Elixir UTFPR (por Adolfo Neto)

Copiei e colei de novo e deu certo!

Collapse
 
wlsf profile image
Willian Frantz

Haha, que bom!

Collapse
 
jpbrab0 profile image
João Pedro Resende

Esse Will é monstro demais