Photo by Tim Bennett on Unsplash

Meu primeiro contato com GraphQL

Deivison Cardoso
gruponewway
Published in
7 min readNov 21, 2019

--

GraphQL é uma linguagem de consulta criada pelo Facebook e considerada uma alternativa para arquiteturas REST. Diferente do tradicional JSON, o GraphQL utiliza uma sintaxe própria, tipada, estruturadas em schemas, que são utilizados para validar tanto a entrada quanto saída dos dados. Para este artigo iremos abordar os passos iniciais, falando sobre Queries e Mutations.

Queries e Mutations são os nomes dos tipos de operações cujas ações são respectivamente de seleção, executando funções que apenas retornam dados da API, ou de mutação, que também executam funções que irão alterar algum dado na API.

Entendendo GraphQL tendo como referência uma arquitetura REST

Para exemplificar melhor, vamos supor a seguinte situação: temos uma API e queremos que ela retorne um produto específico e também uma listagem de todas as categorias de produtos cadastradas em um banco de dados.

Em uma arquitetura REST, é normal nos depararmos com duas rotas do tipo GET, uma que trará o produto desejado e outra que listará todas as categorias. Para conseguir o resultado proposto no nosso exemplo, devemos executar duas requisições para a API.

Quando falamos de GraphQL, geralmente só temos uma rota, do tipo POST, com o endpoint /graphql. Mas como assim? Como a API saberá o que deve ser retornado? É aí que entram as Queries e Mutations.

Para satisfazer o nosso exemplo utilizando GraphQL, seria necessário fazer uma requisição do tipo POST e no corpo da requisição detalhamos que será uma operação do tipo Query com as funções que tragam o produto específico e a listagem das categorias. Note que isto é apenas uma única requisição.

Exemplo de estrutura de uma query GraphQL (vazia)

Outro ponto importante a ser destacado é que, em uma API REST, o cliente não tem controle do que será retornado. Às vezes ele precisa de apenas alguns dados, e a API está retornando um conjunto de dados que em sua maioria não será utilizado. Isso é conhecido como overfetching. O mesmo pode acontecer ao contrário, conhecido como underfetching, onde o cliente pode precisar de certos dados e a API retorna apenas alguns deles.

Com o GraphQL, ao chamar uma função, o cliente informa quais dados ele quer que seja retornado. Por exemplo, posso informar na função que eu quero receber apenas o nome e o preço do produto e todos os campos possíveis das categorias. Isso é fantástico e pode muitas vezes evitar tanto o overfetching e o underfetching.

Exemplo de estrutura de uma query GraphQL passando quais os campos desejados na resposta da API

Para situações onde uma API serve dados tanto para uma webpage (que geralmente consome muitos dados) quanto para um app mobile (consumindo dados em uma escala bem menor) o GraphQL se encaixa como uma luva, não sendo necessário criar rotas específicas para cada plataforma, já que a mesma rota será utilizada para os dois, porém cada um pede na requisição apenas o que irá consumir.

Mas como saber quais funções a API possui, quais parâmetros ela recebe e quais dados ela retorna?

Aí entra a biblioteca a qual me fez ter o primeiro contato com a tecnologia, chamada graphql-yoga. Ela agrupa outras bibliotecas e é bem simples sua configuração. Além disso, ela possui um recurso herdado chamado playground, que cria um endpoint onde é possível testar sua API, escrevendo suas Queries e Mutations e verificando o resultado retornado.

Executando a query dentro do GraphQL Playground

Nesse playground ainda há uma aba lateral chamada Docs, que vai ler os seus arquivos de schemas e montar uma documentação de tudo disponível na API de forma automática, exibindo as funções disponíveis nas Queries e Mutations, os parâmetros, os dados disponíveis para retorno e seus respectivos tipos de dados.

Aba de documentação aberta com a função findProduct selecionada

Vamos para o código

A API completa estará disponível no meu Github, caso queira ver o fonte com mais detalhes. Ela foi escrita em NodeJS com conexão a um banco de dados MongoDB utilizando a biblioteca Mongoose. Não irei detalhar sobre a configuração e conexão com o banco nesse artigo. Vou focar apenas nas partes principais da estrutura da API para utilizar GraphQL.

Antes de detalhar os arquivos, algumas considerações sobre a linguagem:

  1. O GraphQL já possui alguns tipos de dados básicos, chamados de ScalarTypes. São eles: Int, Float, String, Boolean e ID.
  2. Você pode criar seus próprios tipos de dados.
  3. Os tipos seguidos de ! significam que não aceitam valor null
  4. Para indicar uma propriedade será uma listagem, um array, o tipo é envolvido entre colchetes. A regra do ! se aplica aos colchetes também. Para garantir que sempre seja retornado um array, mesmo que vazio, é necessário incluir o ! após o colchetes também.

Primeiro vamos analisar a pasta schemas, onde são colocados os arquivos com extensão .graphql. Criamos os arquivos category.graphql e product.graphql relacionados aos nossos Models e colocamos os dados referente a cada entidade. Nesses arquivos, definimos os schemas, de acordo com as regras listadas acima.

Schemas Category e Product agrupados

Note que no schema Product temos um campo do tipo Category. Isso dá a entender que essas duas entidades terão algum tipo de relacionamento. O GraphQL não precisa se preocupar com isso, como será feito ou de onde vem os dados. Ele só entende que ao retornar, o atributo category do schema Product deverá conter os mesmos atributos que foram definidos no schema Category

Logo após criamos a query.graphql e mutations.graphql que armazenará o nome das funções, seus parâmetros caso haja e o tipo do retorno. Atenção: aqui não é a função em si, é apenas a sua assinatura.

Arquivo query.graphql

Com isso estamos dizendo que a operação Query aceitará uma função chamada listCategories, que não receberá parâmetros, e irá retornar um array com objetos do tipo Category, e caso não tenha nenhum registro, será retornado um array vazio. A função findCategory por sua vez recebe um parâmetro do tipo ID e este não pode ser nulo. Esta função deve retornar um objeto do tipo Category caso encontre algum registro, senão ela retornará null já que não foi colocado o ! após o tipo do retorno. As funções relacionadas ao produto seguem a mesma lógica.

Arquivo mutation.graphql

Nas mutations, quis exemplificar duas formas diferentes. Nas mutations relacionadas a categoria, temos campo a campo especificados nos parâmetros, enquanto que nas funções relacionadas ao produto, temos um atributo do tipo ProductInput. Primeiramente, vamos adicionar o ProductInput ao product.graphql, o nosso schema de produtos.

Novo schema ProductInput

Schemas do tipo input são úteis em parâmetros de funções dentro das operações de mutations, já que essas geralmente recebem um objeto inteiro do lado do cliente. No nosso exemplo acima, caso o cliente fosse fazer uma alteração em uma categoria, ele iria chamar a função updateCategory e passar parâmetro por parâmetro, enquanto que em uma alteração de produto, o cliente iria passar apenas o id, e o objeto inteiro que ele já está trabalhando.

Até agora só declaramos os schemas, as estruturas que os dados entrarão e sairão da API, e também é desses arquivos que o graphql-playground se utilizará para gerar a documentação automática.

Agora na pasta queries temos os arquivos categoryQueries.js que terá a função cuja assinatura deve ser a mesma declarada no schema query.graphql, e é nesta função que terá a lógica que será executada. O mesmo se aplica para o arquivo productQueries.js e também para as mutations, categoryMutations.js e productMutations.js.

Arquivo categoryQueries.js
Arquivo productQueries.js

Para ser sucinto, não mostraremos as mutations aqui, mas caso queira ver, segue o link do Github.

Com todos esses arquivos criados, agora é possível entender a criação do servidor GraphQL no arquivo server.js.

Arquivo server.js

Ao criar uma nova instância do servidor, ele recebe de parâmetro um objeto que possuí um atributo typeDefs que serão todos os nossos arquivos que definem as funções, dados retornados e seus tipos. Utilizei a função loadSchemaFiles da biblioteca graphql-toolkit para que fosse possível ler os arquivos .graphql e converter para objeto.

Também temos o atributo resolvers que por sua vez é um objeto contendo as chaves Query e Mutations, cujo valor de cada chave é um objeto contendo todas as funções correspondente ao seu tipo de operação.

A variável options declarada após a criação do servidor, contém os dados de qual porta o servidor ficará escutando as requisições, qual é o endpoint para as requisições (o padrão adotado é /graphql), subscriptions é relacionado a tempo-real (ainda não utilizei), e por fim o endpoint para acessar o playground que comentamos nesse artigo.

Como foi o meu primeiro contato, ainda não tenho experiência suficiente para tirar conclusões sobre a tecnologia. Acredito que uma migração de uma API REST para uma API GraphQL não seria muito sutil porque qualquer operação, inclusive as que dão erro, retornam com código 200 (OK). Os erros vêm listados no corpo da resposta em uma chave errors. Isso acontece porque pode haver uma query igual a que utilizamos no exemplo desse artigo, executando duas funções em uma requisição, e pode dar erro em apenas uma delas. Isso retornaria uma resposta 200 com o resultado da função que rodou corretamente, e o erro da outra.

Para casos onde é uma API nova, servindo dados para diversos tipos de aplicações (app, web) acredito que seria uma proposta muito interessante dado alguns pontos discutidos, principalmente o overfetching em apps. Mas essa é apenas a primeira impressão de alguém que se empolgou enquanto aprendia uma pequena parcela do que a tecnologia tem a oferecer. Ainda há muito que aprender sobre.

--

--

No responses yet