A idéia deste repositório é documentar meus aprendizados ao longo dos estudos de GraphQL com base no livro The Road to GraphQL, de Robin Wieruch.
Antes do GraphQL, a solução mais utilizada para transferência de dados entre cliente e servidor eram APIs REST.
APIs REST disponibilizam URLs para recursos específicos (ex.: usuários, projetos, produtos, etc.) e tais recursos (ou entidades) possuem certos atributos (ex.: id, nome, data de criação, data da última atualização, etc.)
Em APIs REST, para cada recurso, podemos realizar diferentes operações, definidas por verbos (ex.: GET, POST, PUT, DELETE, etc.)
Além disso, quando a aplicação cliente necessita de dados de diferentes entidades (ex.: últimos 5 blog posts do usuário 23), múltiplas requisições devem ser realizadas (ex.: GET /users/23
e GET /blogs?user_id=23&limit=5
).
Um problema dessa abordagem (dependendo to contexto) pode ser o que em inglês descreve-se como overfetching (o qual traduzo livremente como excesso de chamadas via rede).
Imagine que os dados de certas entidades (ex.: usuários e blogs) estão disponíveis em um mesmo servidor e diferentes aplicações cliente consomem dados deste servidor através de sua API. No entanto, estes diferentes clientes tem diferentes necessidades, ou até mesmo diferentes limitações.
Uma aplicação web desktop, por exemplo, tem o "luxo" de ter mais espaço em tela para exibir informações. Além disso, em certos casos, os dispositivos que rodam tais aplicações possuem mais poder computacional, ou até mesmo, internet com velocidade mais rápida (ex.: rede de fibra ótica).
Neste caso, fazer mais de uma requisição para buscar informações à serem exibidas na tela pode não ser um grande problema.
Além disso, visto o maior espaço em tela, mais campos podem ser exibidos, ou seja, mais atributos das entidades.
Já uma aplicação cliente para dispositivos móveis tem que lidar com telas menores, e por exemplo, com uma internet de velocidade lenta (ex.: 3G). Neste caso, há um grande custo em cada requisição feita pela rede.
Além disso, nem todos os atributos retornados por tais requisições poderão ser exibidos na tela, visto a limitação de seu tamanho. Neste caso, só os atributos mais importantes são exibidos e os outros atributos trafegados pela rede foram um desperdício.
Com isso, no mundo REST, para resolver tais situações, APIs específicas por cliente devem ser desenvolvidas para evitar desperdícios. Porém, isso gera mais código pra ser mantido.
Por fim, no mundo das APIs REST, o lado do servidor é quem define o que está disponível para seus clientes.
Segue abaixo uma lista de APIs REST:
GraphQL (ou Graph Query Language) é uma linguagem open-source criada pelo Facebook que resolve alguns dos problemas do mundo das APIs REST, onde é possível fazer uma única chamada via rede para buscar exatamente os dados que fazem sentido cada aplicação cliente (mesmo que tais dados provenham de diferentes entidades), evitando o problema do overfetching.
Em uma API GraphQL, só uma URL é disponível, e em vez de operações por verbos (como GET, POST, etc.), temos operações de queries (leitura) e mutations (escrita).
Em GraphQL, entidades possuem campos. Além disso, GraphQL é uma linguagem fortemente tipada, portanto, cada campo possui seu proprio tipo (os quais podem ser tipos primitivos, como String, Int, Boolean, ou tipos customizados, como User, por exemplo).
Diferente de APIs REST, com GraphQL a perspectiva muda, onde as aplicações cliente são quem decide o que querem, em vez do servidor.
No lado do "servidor", o que temos são esquemas GraphQL (GraphQL Schemas), os quais podem ser múltiplos (por exemplo, em uma arquitetura de micro-serviços), e um esquema consolidado pode ser criado para consumo pela aplicação principal (utilizando o mecanismo de GraphQL Schema Stitching).
A API GraphQL do GitHub será utilizada para os estudos a seguir.
Para explorar a API GraphQL do GitHub, o GitHub GraphQL API Explorer será utilizado, como uma alternativa que roda no navegador, sem a necessidade de nada mais além de uma conta no GitHub.
Abaixo seguem exemplos de queries com suas devidas explicações e retornos.
query {
viewer {
login
}
}
{
"data": {
"viewer": {
"login": "wlsf82"
}
}
}
query {
viewer {
login
name
url
}
}
{
"data": {
"viewer": {
"login": "wlsf82",
"name": "Walmyr",
"url": "https://github.com/wlsf82"
}
}
}
Query 3 - Busca os valores name
e url
a partir da entidade organization
, passando o login
de tal entidade como parâmetro da query
query {
organization(login: "facebook") {
name
url
}
}
{
"data": {
"organization": {
"name": "Meta",
"url": "https://github.com/facebook"
}
}
}
Query 4 - Busca os valores name
e url
a partir da entidade organization
, porém, para duas organizações, portanto, dando alias para cada uma, para evitar conflito de campos
query {
facebook: organization(login: "facebook") {
name
url
}
cypress: organization(login: "cypress-io") {
name
url
}
}
{
"data": {
"facebook": {
"name": "Meta",
"url": "https://github.com/facebook"
},
"cypress": {
"name": "Cypress.io",
"url": "https://github.com/cypress-io"
}
}
}
Query 5 - Busca os valores name
e url
a partir da entidade organization
, para duas organizações, portanto, dando alias para cada uma, porém, usando um fragmento, para evitar duplicação de código
query {
facebook: organization(login: "facebook") {
...orgSharedFields
}
cypress: organization(login: "cypress-io") {
...orgSharedFields
}
}
fragment orgSharedFields on Organization{
name
url
}
{
"data": {
"facebook": {
"name": "Meta",
"url": "https://github.com/facebook"
},
"cypress": {
"name": "Cypress.io",
"url": "https://github.com/cypress-io"
}
}
}
Query 6 - Busca valores em comum a partir da entidade organization
com o uso de fragmentos, em conjunto com dados específicos para um determinado objeto da query
query {
facebook: organization(login: "facebook") {
...orgSharedFields
login
}
cypress: organization(login: "cypress-io") {
...orgSharedFields
}
}
fragment orgSharedFields on Organization{
name
url
}
{
"data": {
"facebook": {
"name": "Meta",
"url": "https://github.com/facebook",
"login": "facebook"
},
"cypress": {
"name": "Cypress.io",
"url": "https://github.com/cypress-io"
}
}
}
Query 7 - Busca valores da entidade organization
onde o parâmetro login
é obrigatório e passado via uma variável $org
query ($org: String!) {
organization(login: $org) {
name
url
}
}
{
"org": "cypress-io"
}
{
"data": {
"organization": {
"name": "Cypress.io",
"url": "https://github.com/cypress-io"
}
}
}
Query 8 - Busca valores da entidade organization
onde o parâmetro login
NÃO é obrigatório, a variável $org
possui um valor default, porém, tal variável NÃO está definida
query ($org: String = "cypress-io") {
organization(login: $org) {
name
url
}
}
{}
{
"data": {
"organization": {
"name": "Cypress.io",
"url": "https://github.com/cypress-io"
}
}
}
Query 9 - Busca valores da entidade organization
onde o parâmetro login
NÃO é obrigatório, a variável $org
possui um valor default, porém, tal variável está definida com um valor DIFERENTE do valor default
query ($org: String = "cypress-io") {
organization(login: $org) {
name
url
}
}
{
"org": "facebook"
}
{
"data": {
"organization": {
"name": "Meta",
"url": "https://github.com/facebook"
}
}
}
Query 10 - Busca os valores name
e url
a partir da entidade organization
, para duas organizações, portanto, dando alias para cada uma. Além disso, usa dois fragmentos sob a mesma entidade (Organization
) para evitar duplicação de código e exemplificar o uso de mais de um fragmento na mesma consulta
query {
facebook: organization(login: "facebook") {
...sharedFields1
...sharedFields2
}
cypress: organization(login: "cypress-io") {
...sharedFields1
...sharedFields2
}
}
fragment sharedFields1 on Organization{
name
}
fragment sharedFields2 on Organization{
url
}
{
"data": {
"facebook": {
"name": "Meta",
"url": "https://github.com/facebook"
},
"cypress": {
"name": "Cypress.io",
"url": "https://github.com/cypress-io"
}
}
}
Query 11 - Busca os valores name
e url
a partir da entidade organization
utilizando uma query
nomeada. Algo como uma função nomeada em JavaScript, em vez de uma função anônima. Útil para deixar claro o propsósito da query de forma declarativa
query OrganizationForLearningReact($organization: String!) {
organization(login: $organization) {
name
url
}
}
{
"organization": "the-road-to-learn-react"
}
{
"data": {
"organization": {
"name": "The Road to React",
"url": "https://github.com/the-road-to-learn-react"
}
}
}
Query 12 - Busca os valores name
e url
a partir da entidade organization
, e então, a partir da entidade repository
, busca o valor da propriedade name
. O nome disso é aninhamento de objetos (nested objects). Para ambas entidades (organization
e repository
), seus parâmetros obrigatórios ($organization
e $repository
) são passados com o uso de variáveis
query OrganizationForLearningReact(
$organization: String!,
$repository: String!
) {
organization(login: $organization) {
name
url
repository(name: $repository) {
name
}
}
}
{
"organization": "the-road-to-learn-react",
"repository": "the-road-to-learn-react-portuguese"
}
{
"data": {
"organization": {
"name": "The Road to React",
"url": "https://github.com/the-road-to-learn-react",
"repository": {
"name": "the-road-to-learn-react-portuguese"
}
}
}
}