GraphQL vs. REST - Um tutorial do GraphQL
Publicados: 2022-03-11Você pode ter ouvido sobre o novo garoto ao redor do quarteirão: GraphQL. Caso contrário, o GraphQL é, em uma palavra, uma nova maneira de buscar APIs, uma alternativa ao REST. Começou como um projeto interno no Facebook e, como era de código aberto, ganhou muita força.
O objetivo deste artigo é ajudá-lo a fazer uma transição fácil do REST para o GraphQL, se você já se decidiu pelo GraphQL ou está apenas disposto a experimentá-lo. Nenhum conhecimento prévio do GraphQL é necessário, mas é necessário alguma familiaridade com APIs REST para entender o artigo.
A primeira parte do artigo começará dando três razões pelas quais eu pessoalmente acho que o GraphQL é superior ao REST. A segunda parte é um tutorial sobre como adicionar um endpoint GraphQL em seu back-end.
Graphql vs. REST: Por que descartar REST?
Se você ainda está hesitando se o GraphQL é adequado ou não para suas necessidades, uma visão geral bastante ampla e objetiva de “REST vs. GraphQL” é fornecida aqui. No entanto, para meus três principais motivos para usar o GraphQL, continue lendo.
Razão 1: Desempenho da Rede
Digamos que você tenha um recurso de usuário no back-end com nome, sobrenome, email e 10 outros campos. No cliente, você geralmente só precisa de alguns deles.
Fazer uma chamada REST no endpoint /users devolve todos os campos do usuário, e o cliente usa apenas os que precisa. Há claramente algum desperdício de transferência de dados, o que pode ser considerado em clientes móveis.
O GraphQL, por padrão, busca os menores dados possíveis. Se você precisar apenas de nomes e sobrenomes de seus usuários, especifique isso em sua consulta.
A interface abaixo é chamada GraphiQL, que é como um explorador de API para GraphQL. Eu criei um pequeno projeto para o propósito deste artigo. O código está hospedado no GitHub e falaremos sobre ele na segunda parte.
No painel esquerdo da interface está a consulta. Aqui, estamos buscando todos os usuários - faríamos GET /users com REST - e obtendo apenas seus nomes e sobrenomes.
Consulta
query { users { firstname lastname } }Resultado
{ "data": { "users": [ { "firstname": "John", "lastname": "Doe" }, { "firstname": "Alicia", "lastname": "Smith" } ] } }Se também quisermos receber os e-mails, adicionar uma linha “email” abaixo de “lastname” resolveria o problema.
Alguns back-ends REST oferecem opções como /users?fields=firstname,lastname para retornar recursos parciais. Pelo que vale a pena, o Google recomenda. No entanto, ele não é implementado por padrão e torna a solicitação pouco legível, especialmente quando você adiciona outros parâmetros de consulta:
-
&status=activepara filtrar usuários ativos -
&sort=createdAatpara classificar os usuários pela data de criação -
&sortDirection=descporque você obviamente precisa -
&include=projectspara incluir os projetos dos usuários
Esses parâmetros de consulta são patches adicionados à API REST para imitar uma linguagem de consulta. GraphQL é acima de tudo uma linguagem de consulta, que torna as solicitações concisas e precisas desde o início.
Razão 2: A escolha de design "Incluir vs. Endpoint"
Vamos imaginar que queremos construir uma ferramenta simples de gerenciamento de projetos. Temos três recursos: usuários, projetos e tarefas. Também definimos as seguintes relações entre os recursos:
Aqui estão alguns dos endpoints que expomos ao mundo:
| Ponto final | Descrição |
|---|---|
GET /users | Listar todos os usuários |
GET /users/:id | Obtenha o usuário único com id :id |
GET /users/:id/projects | Obtenha todos os projetos de um usuário |
Os endpoints são simples, de fácil leitura e bem organizados.
As coisas ficam mais complicadas quando nossos pedidos ficam mais complexos. Vamos pegar o endpoint GET /users/:id/projects : Digamos que eu queira mostrar apenas os títulos dos projetos na página inicial, mas projetos+tarefas no painel, sem fazer várias chamadas REST. Eu chamaria:
-
GET /users/:id/projectspara a página inicial. -
GET /users/:id/projects?include=tasks(por exemplo) na página do painel para que o back-end anexe todas as tarefas relacionadas.
É uma prática comum adicionar parâmetros de consulta ?include=... para fazer isso funcionar, e é até recomendado pela especificação da API JSON. Parâmetros de consulta como ?include=tasks ainda podem ser lidos, mas em pouco tempo, terminaremos com ?include=tasks,tasks.owner,tasks.comments,tasks.comments.author .
Nesse caso, seria mais sensato criar um endpoint /projects para fazer isso? Algo como /projects?userId=:id&include=tasks , pois teríamos um nível de relacionamento a menos para incluir? Ou, na verdade, um ponto de extremidade /tasks?userId=:id também pode funcionar. Essa pode ser uma escolha de design difícil, ainda mais complicada se tivermos um relacionamento de muitos para muitos.
O GraphQL usa a abordagem include em todos os lugares. Isso torna a sintaxe para buscar relacionamentos poderosa e consistente.
Aqui está um exemplo de busca de todos os projetos e tarefas do usuário com id 1.
Consulta
{ user(id: 1) { projects { name tasks { description } } } }Resultado
{ "data": { "user": { "projects": [ { "name": "Migrate from REST to GraphQL", "tasks": [ { "description": "Read tutorial" }, { "description": "Start coding" } ] }, { "name": "Create a blog", "tasks": [ { "description": "Write draft of article" }, { "description": "Set up blog platform" } ] } ] } } }Como você pode ver, a sintaxe da consulta é facilmente legível. Se quiséssemos ir mais fundo e incluir tarefas, comentários, imagens e autores, não pensaríamos duas vezes em como organizar nossa API. O GraphQL facilita a busca de objetos complexos.
Razão 3: Gerenciando Diferentes Tipos de Clientes
Ao construir um back-end, sempre começamos tentando tornar a API o mais amplamente utilizável possível por todos os clientes. No entanto, os clientes sempre querem chamar menos e buscar mais. Com inclusões profundas, recursos parciais e filtragem, as solicitações feitas por clientes da Web e móveis podem diferir muito umas das outras.
Com REST, existem algumas soluções. Podemos criar um endpoint personalizado (ou seja, um endpoint de alias, por exemplo, /mobile_user ), uma representação personalizada ( Content-Type: application/vnd.rest-app-example.com+v1+mobile+json ), ou até mesmo um cliente -específica API (como a Netflix fez uma vez). Todos os três exigem esforço extra da equipe de desenvolvimento de back-end.
GraphQL dá mais poder ao cliente. Se o cliente precisar de solicitações complexas, ele mesmo construirá as consultas correspondentes. Cada cliente pode consumir a mesma API de forma diferente.
Como começar com GraphQL
Na maioria dos debates sobre “GraphQL vs. REST” hoje, as pessoas pensam que devem escolher qualquer um dos dois. Isto simplesmente não é verdade.
Os aplicativos modernos geralmente usam vários serviços diferentes, que expõem várias APIs. Na verdade, poderíamos pensar no GraphQL como um gateway ou um wrapper para todos esses serviços. Todos os clientes atingiriam o endpoint GraphQL e esse endpoint atingiria a camada de banco de dados, um serviço externo como ElasticSearch ou Sendgrid ou outros endpoints REST.
Uma segunda maneira de usar ambos é ter um endpoint /graphql separado em sua API REST. Isso é especialmente útil se você já tiver vários clientes acessando sua API REST, mas quiser experimentar o GraphQL sem comprometer a infraestrutura existente. E esta é a solução que estamos explorando hoje.
Como dito anteriormente, vou ilustrar este tutorial com um pequeno projeto de exemplo, disponível no GitHub. É uma ferramenta simplificada de gerenciamento de projetos, com usuários, projetos e tarefas.
As tecnologias utilizadas para este projeto são Node.js e Express para o servidor web, SQLite como banco de dados relacional e Sequelize como ORM. Os três modelos — usuário, projeto e tarefa — são definidos na pasta de models . Os endpoints REST /api/users , /api/projects e /api/tasks são expostos ao mundo e são definidos na pasta rest .

Observe que o GraphQL pode ser instalado em qualquer tipo de back-end e banco de dados, usando qualquer linguagem de programação. As tecnologias utilizadas aqui são escolhidas por uma questão de simplicidade e legibilidade.
Nosso objetivo é criar um endpoint /graphql sem remover os endpoints REST. O endpoint GraphQL atingirá o ORM do banco de dados diretamente para buscar dados, de modo que seja totalmente independente da lógica REST.
Tipos
O modelo de dados é representado no GraphQL por tipos , que são fortemente tipados. Deve haver um mapeamento de 1 para 1 entre seus modelos e tipos de GraphQL. Nosso tipo de User seria:
type User { id: ID! # The "!" means required firstname: String lastname: String email: String projects: [Project] # Project is another GraphQL type }Consultas
As consultas definem quais consultas você pode executar em sua API GraphQL. Por convenção, deve haver um RootQuery , que contém todas as consultas existentes. Também apontei o equivalente REST de cada consulta:
type RootQuery { user(id: ID): User # Corresponds to GET /api/users/:id users: [User] # Corresponds to GET /api/users project(id: ID!): Project # Corresponds to GET /api/projects/:id projects: [Project] # Corresponds to GET /api/projects task(id: ID!): Task # Corresponds to GET /api/tasks/:id tasks: [Task] # Corresponds to GET /api/tasks }Mutações
Se as consultas são solicitações GET , as mutações podem ser vistas como solicitações POST / PATCH / PUT / DELETE (embora na verdade sejam versões sincronizadas de consultas).
Por convenção, colocamos todas as nossas mutações em um RootMutation :
type RootMutation { createUser(input: UserInput!): User # Corresponds to POST /api/users updateUser(id: ID!, input: UserInput!): User # Corresponds to PATCH /api/users removeUser(id: ID!): User # Corresponds to DELETE /api/users createProject(input: ProjectInput!): Project updateProject(id: ID!, input: ProjectInput!): Project removeProject(id: ID!): Project createTask(input: TaskInput!): Task updateTask(id: ID!, input: TaskInput!): Task removeTask(id: ID!): Task } Observe que introduzimos novos tipos aqui, chamados UserInput , ProjectInput e TaskInput . Esta é uma prática comum com REST também, para criar um modelo de dados de entrada para criar e atualizar recursos. Aqui, nosso tipo UserInput é nosso tipo User sem os campos id e projects e observe a palavra-chave input em vez de type :
input UserInput { firstname: String lastname: String email: String }Esquema
Com tipos, consultas e mutações, definimos o esquema GraphQL , que é o que o endpoint GraphQL expõe ao mundo:
schema { query: RootQuery mutation: RootMutation }Esse esquema é fortemente tipado e é o que nos permitiu ter esses autocompletes úteis no GraphiQL.
Resolvedores
Agora que temos o esquema público, é hora de dizer ao GraphQL o que fazer quando cada uma dessas consultas/mutações for solicitada. Resolvedores fazem o trabalho duro; eles podem, por exemplo:
- Acerte um endpoint REST interno
- Chamar um microsserviço
- Acesse a camada de banco de dados para fazer operações CRUD
Estamos escolhendo a terceira opção em nosso aplicativo de exemplo. Vamos dar uma olhada no nosso arquivo de resolvedores:
const models = sequelize.models; RootQuery: { user (root, { id }, context) { return models.User.findById(id, context); }, users (root, args, context) { return models.User.findAll({}, context); }, // Resolvers for Project and Task go here }, /* For reminder, our RootQuery type was: type RootQuery { user(id: ID): User users: [User] # Other queries } Isso significa que, se a consulta user(id: ID!) for solicitada no GraphQL, retornaremos User.findById() , que é uma função Sequelize ORM, do banco de dados.
Que tal juntar outros modelos no pedido? Bem, precisamos definir mais resolvedores:
User: { projects (user) { return user.getProjects(); // getProjects is a function managed by Sequelize ORM } }, /* For reminder, our User type was: type User { projects: [Project] # We defined a resolver above for this field # ...other fields } */ Portanto, quando solicitamos o campo de projects em um tipo de User no GraphQL, essa junção será anexada à consulta do banco de dados.
E, finalmente, resolvedores para mutações:
RootMutation: { createUser (root, { input }, context) { return models.User.create(input, context); }, updateUser (root, { id, input }, context) { return models.User.update(input, { ...context, where: { id } }); }, removeUser (root, { id }, context) { return models.User.destroy(input, { ...context, where: { id } }); }, // ... Resolvers for Project and Task go here } Você pode brincar com isso aqui. Para manter os dados no servidor limpos, desativei os resolvedores para mutações, o que significa que as mutações não farão nenhuma operação de criação, atualização ou exclusão no banco de dados (e, portanto, retornarão null na interface).
Consulta
query getUserWithProjects { user(id: 2) { firstname lastname projects { name tasks { description } } } } mutation createProject { createProject(input: {name: "New Project", UserId: 2}) { id name } }Resultado
{ "data": { "user": { "firstname": "Alicia", "lastname": "Smith", "projects": [ { "name": "Email Marketing Campaign", "tasks": [ { "description": "Get list of users" }, { "description": "Write email template" } ] }, { "name": "Hire new developer", "tasks": [ { "description": "Find candidates" }, { "description": "Prepare interview" } ] } ] } } }Pode levar algum tempo para reescrever todos os tipos, consultas e resolvedores do seu aplicativo existente. No entanto, existem muitas ferramentas para ajudá-lo. Por exemplo, existem ferramentas que traduzem um esquema SQL para um esquema GraphQL, incluindo resolvedores!
Juntando tudo
Com um esquema bem definido e resolvedores sobre o que fazer em cada consulta do esquema, podemos montar um endpoint /graphql em nosso back-end:
// Mount GraphQL on /graphql const schema = makeExecutableSchema({ typeDefs, // Our RootQuery and RootMutation schema resolvers: resolvers() // Our resolvers }); app.use('/graphql', graphqlExpress({ schema }));E podemos ter uma interface GraphiQL bonita em nosso back-end. Para fazer uma solicitação sem GraphiQL, basta copiar a URL da solicitação e executá-la com cURL, AJAX ou diretamente no navegador. Claro, existem alguns clientes GraphQL para ajudá-lo a construir essas consultas. Veja abaixo alguns exemplos.
Qual é o próximo?
O objetivo deste artigo é dar uma amostra de como é o GraphQL e mostrar que é definitivamente possível experimentar o GraphQL sem jogar fora sua infraestrutura REST. A melhor maneira de saber se o GraphQL atende às suas necessidades é experimentá-lo você mesmo. Espero que este artigo faça você mergulhar.
Há muitos recursos sobre os quais não discutimos neste artigo, como atualizações em tempo real, lotes do lado do servidor, autenticação, autorização, cache do lado do cliente, upload de arquivos etc. Um excelente recurso para aprender sobre esses recursos é Como GraphQL.
Abaixo estão alguns outros recursos úteis:
| Ferramenta do lado do servidor | Descrição |
|---|---|
graphql-js | A implementação de referência do GraphQL. Você pode usá-lo com express-graphql para criar um servidor. |
graphql-server | Um servidor GraphQL completo criado pela equipe Apollo. |
| Implementações para outras plataformas | Ruby, PHP, etc. |
| Ferramenta do lado do cliente | Descrição |
|---|---|
| Retransmissão | Um framework para conectar React com GraphQL. |
| apolo-cliente. | Um cliente GraphQL com associações para React, Angular 2 e outras estruturas front-end. |
Concluindo, acredito que o GraphQL é mais do que hype. Ele ainda não substituirá o REST amanhã, mas oferece uma solução de alto desempenho para um problema genuíno. É relativamente novo, e as melhores práticas ainda estão em desenvolvimento, mas definitivamente é uma tecnologia sobre a qual ouviremos falar nos próximos dois anos.
