Consumindo GraphQL com Elixir?
Willian Frantz
Posted on June 2, 2022
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!
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:
- Criar um serviço backend de CRUD (Create, Read, Update e Delete) para recursos.
- 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 }
}
}
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} } }"}'
(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"
}
}
}
}
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:
- httpoison:1.8 (Cliente HTTP, para requisições)
- jason:1.3 (Conversor de JSON)
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:
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! 💜💚
Posted on June 2, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.