Carregando
Todos os artigos
Programação 21 min de leitura

Primeiros Passos com GraphQL e Express: Um Guia Visual e Prático

Construa a sua primeira API GraphQL com Express, passo a passo: schemas, tipos, resolvers, argumentos, dados aninhados, mutações e variáveis, o playground GraphiQL, respostas reais, uma comparação com REST medindo bytes e pedidos, e exercícios com respostas.

nbkhope
Nesta página

Um aplicativo de celular mostra uma lista de frutas com as suas receitas. Com uma API REST clássica, ele chamaria /frutas, depois /frutas/1/receitas, /frutas/2/receitas e assim por diante, e cada resposta traria campos que a tela nunca mostra. O GraphQL inverte isso: o aplicativo manda um pedido descrevendo exatamente os dados que quer, e o servidor responde exatamente nesse formato, nem mais, nem menos.

Neste guia você vai construir uma API GraphQL com Node.js e Express, um passo de cada vez: uma primeira consulta, os seus próprios tipos, argumentos, dados aninhados, mutações e variáveis, além do playground GraphiQL no navegador. Pelo caminho você vai ver cada resposta real, medir o que o GraphQL economiza em comparação com o REST e terminar com exercícios.

Todas as respostas abaixo vêm de execuções reais com Node.js 26, Express 5.3, graphql 16.14, graphql-http 1.23 e Ruru 2.0.

REST vs. GraphQL numa imagem

REST vs. GraphQL para uma tela que lista frutas com as suas receitas. Com REST, o aplicativo faz um pedido para /frutas e depois mais um por fruta para /frutas/1/receitas, /frutas/2/receitas e /frutas/3/receitas: 4 idas e voltas, e cada resposta traz todos os campos, sejam usados pela tela ou não. Com GraphQL, o aplicativo manda um único POST para /graphql com uma consulta que diz exatamente os campos que quer, frutas com nome e receitas com nome, e recebe uma resposta exatamente nesse formato. O REST tem muitos endpoints com respostas fixas; o GraphQL tem um endpoint e o cliente escolhe os campos

APIs REST têm muitos endpoints, cada um devolvendo um formato fixo. Isso leva a dois problemas:

  • Dados demais (over-fetching): as respostas trazem campos de que o aplicativo não precisa, desperdiçando banda, principalmente em conexões móveis lentas.
  • Dados de menos (under-fetching): uma resposta não basta, então o aplicativo faz idas e voltas extras para juntar o resto.

O GraphQL tem um endpoint, normalmente POST /graphql. O cliente manda uma consulta (query) que lista os campos que quer, e um schema no servidor descreve tudo o que pode ser pedido.

Como um pedido GraphQL funciona

Como um pedido GraphQL passa por um servidor Express. 1: o cliente manda POST /graphql com um corpo JSON contendo o texto da consulta. 2: o Express encaminha para o tratador do graphql-http. 3: o GraphQL analisa o texto da consulta. 4: valida a consulta com o schema; campos desconhecidos ou argumentos faltando param aqui com uma resposta de erros. 5: executa a consulta, chamando uma função resolver para cada campo pedido. 6: devolve um JSON com uma chave data no mesmo formato da consulta

Você escreve duas coisas: o schema (o que pode ser pedido, com tipos) e os resolvers (funções que buscam o valor de cada campo). Analisar, validar e montar a resposta fica por conta das bibliotecas.

Preparando tudo

Crie um projeto e instale quatro pacotes:

1mkdir api-de-frutas
2cd api-de-frutas
3npm init -y
4npm install express graphql graphql-http ruru
  • express: o servidor web.
  • graphql: a implementação de referência do GraphQL para JavaScript (schemas, tipos, execução).
  • graphql-http: liga o GraphQL ao Express, tratando os pedidos POST /graphql.
  • ruru: serve o GraphiQL, o playground no navegador para testar consultas.

O seu primeiro schema

Comece com a menor API possível: um campo, fruta, que sempre responde "Banana". Coloque isto no schema.js:

1// schema.js
2const { GraphQLObjectType, GraphQLSchema, GraphQLString } = require("graphql");
3
4const TipoConsulta = new GraphQLObjectType({
5  name: "Query",
6  fields: {
7    fruta: {
8      type: GraphQLString,
9      resolve() {
10        return "Banana";
11      },
12    },
13  },
14});
15
16module.exports = new GraphQLSchema({ query: TipoConsulta });
  • O tipo Query é a porta de entrada: os campos dele são as coisas que os clientes podem pedir. (Por convenção, ele se chama Query em inglês mesmo.)
  • Cada campo tem um type (aqui, uma string) e uma função resolve que produz o seu valor.

Agora o servidor, no server.js:

1// server.js
2const express = require("express");
3const { createHandler } = require("graphql-http/lib/use/express");
4const { ruruHTML } = require("ruru/server");
5const schema = require("./schema");
6
7const app = express();
8const PORTA = 3000;
9
10app.all("/graphql", createHandler({ schema }));
11
12app.get("/", (pedido, resposta) => {
13  resposta.type("html");
14  resposta.end(ruruHTML({ endpoint: "/graphql" }));
15});
16
17app.listen(PORTA, () => {
18  console.log(`Servidor GraphQL rodando em http://localhost:${PORTA}/graphql`);
19});

O createHandler({ schema }) é toda a integração com o GraphQL: todo pedido para /graphql passa por ele. A segunda rota serve o playground GraphiQL na página inicial (já chegamos lá). Inicie o servidor:

$ node server.js
Servidor GraphQL rodando em http://localhost:3000/graphql

Um pedido GraphQL é um POST HTTP comum com um corpo JSON contendo o texto da consulta. Dá para mandar um com o curl, de um segundo terminal:

$ curl -X POST http://localhost:3000/graphql -H "Content-Type: application/json" -d '{"query": "{ fruta }"}'
{"data":{"fruta":"Banana"}}

A consulta { fruta } pede o campo fruta, e a resposta volta dentro de uma chave data, no mesmo formato.

Um cliente pequenininho

Escrever JSON entre aspas na linha de comando cansa rápido (e muda de um terminal para outro), então aqui vai um pequeno cliente Node.js, o perguntar.js, que manda uma consulta e imprime a resposta bem formatada:

1// perguntar.js: manda uma consulta GraphQL para o servidor e imprime a resposta
2async function perguntar(consulta, variaveis) {
3  const resposta = await fetch("http://localhost:3000/graphql", {
4    method: "POST",
5    headers: { "Content-Type": "application/json" },
6    body: JSON.stringify({ query: consulta, variables: variaveis }),
7  });
8  console.log(JSON.stringify(await resposta.json(), null, 2));
9}
10
11perguntar(process.argv[2], process.argv[3] && JSON.parse(process.argv[3]));
1$ node perguntar.js "{ fruta }"
2{
3  "data": {
4    "fruta": "Banana"
5  }
6}

É exatamente isso que um aplicativo web ou de celular faz por baixo dos panos: um fetch com um POST levando a consulta.

Testando consultas no GraphiQL

Abra http://localhost:3000/ no navegador e você ganha o GraphiQL (servido pelo Ruru): digite uma consulta à esquerda, aperte o botão de play e a resposta aparece à direita. Ele completa os nomes dos campos a partir do seu schema e sublinha os erros enquanto você digita. Aqui ele está rodando uma consulta que aparece mais adiante neste guia:

O playground GraphiQL servido pelo Ruru em localhost:3000. À esquerda, a consulta FrutaComReceitas pede a fruta de id "1" com nome, calorias e receitas com nome e minutos. À direita, a resposta JSON: data, fruta, nome "Banana", calorias 89, e as receitas "Pão de banana" com 70 minutos e "Vitamina de banana" com 5 minutos. Um botão de play roda a consulta; botões ao lado formatam, juntam e copiam a consulta, e um explorador de documentação permite navegar pelo schema

Criando o seu próprio tipo

Uma string sozinha não é muito interessante. Vamos transformar a fruta num objeto com vários campos, usando um novo GraphQLObjectType:

1// schema.js
2const {
3  GraphQLBoolean,
4  GraphQLID,
5  GraphQLInt,
6  GraphQLObjectType,
7  GraphQLSchema,
8  GraphQLString,
9} = require("graphql");
10
11const TipoFruta = new GraphQLObjectType({
12  name: "Fruta",
13  fields: {
14    id: { type: GraphQLID },
15    nome: { type: GraphQLString },
16    descricao: { type: GraphQLString },
17    ehGostosa: { type: GraphQLBoolean },
18    calorias: { type: GraphQLInt },
19  },
20});
21
22const TipoConsulta = new GraphQLObjectType({
23  name: "Query",
24  fields: {
25    fruta: {
26      type: TipoFruta,
27      resolve() {
28        return {
29          id: "1",
30          nome: "Banana",
31          descricao: "Doce e fácil de descascar",
32          ehGostosa: true,
33          calorias: 89,
34        };
35      },
36    },
37  },
38});
39
40module.exports = new GraphQLSchema({ query: TipoConsulta });

Cada campo ganha um dos tipos escalares que vêm com o GraphQL: GraphQLID (um identificador único, sempre enviado como string), GraphQLString, GraphQLBoolean, GraphQLInt e GraphQLFloat para decimais. O tipo do campo fruta agora é o TipoFruta, e o resolver dele devolve um objeto. Os campos não têm resolvers próprios: o resolver padrão do GraphQL simplesmente lê a propriedade de mesmo nome desse objeto.

Reinicie o servidor (pare com Ctrl+C e rode node server.js de novo) e peça todos os campos:

1$ node perguntar.js "{ fruta { id nome descricao ehGostosa calorias } }"
2{
3  "data": {
4    "fruta": {
5      "id": "1",
6      "nome": "Banana",
7      "descricao": "Doce e fácil de descascar",
8      "ehGostosa": true,
9      "calorias": 89
10    }
11  }
12}

Agora a parte mágica. Peça só dois campos:

1$ node perguntar.js "{ fruta { id nome } }"
2{
3  "data": {
4    "fruta": {
5      "id": "1",
6      "nome": "Banana"
7    }
8  }
9}
O formato da consulta é o formato da resposta. A consulta fruta com id e nome, à esquerda, produz uma resposta à direita em que fruta guarda exatamente id "1" e nome "Banana". Os campos não pedidos, descricao, ehGostosa e calorias, nem são enviados. Cada campo que você lista aparece na resposta, com o mesmo aninhamento; cada campo que você deixa de fora não custa nada

Você recebe exatamente os campos que pediu. Uma página de produto poderia pedir tudo; uma tela de lista poderia pedir só id e nome, da mesma API.

Uma API de verdade: listas, argumentos e dados aninhados

APIs reais servem muitos registros, buscam coisas pelo ID e ligam registros entre si. Vamos guardar os dados num arquivo próprio, fazendo o papel de um banco de dados:

1// dados.js: um "banco de dados" em memória para os exemplos
2const frutas = [
3  { id: "1", nome: "Banana", descricao: "Doce e fácil de descascar", ehGostosa: true, calorias: 89 },
4  { id: "2", nome: "Maçã", descricao: "Crocante e suculenta", ehGostosa: true, calorias: 52 },
5  { id: "3", nome: "Durião", descricao: "Famoso pelo cheiro forte", ehGostosa: false, calorias: 147 },
6];
7
8const receitas = [
9  { frutaId: "1", nome: "Pão de banana", minutos: 70 },
10  { frutaId: "1", nome: "Vitamina de banana", minutos: 5 },
11  { frutaId: "2", nome: "Torta de maçã", minutos: 90 },
12];
13
14module.exports = { frutas, receitas };

E o schema completo:

1// schema.js
2const {
3  GraphQLBoolean,
4  GraphQLID,
5  GraphQLInt,
6  GraphQLList,
7  GraphQLNonNull,
8  GraphQLObjectType,
9  GraphQLSchema,
10  GraphQLString,
11} = require("graphql");
12const { frutas, receitas } = require("./dados");
13
14const TipoReceita = new GraphQLObjectType({
15  name: "Receita",
16  fields: {
17    nome: { type: GraphQLString },
18    minutos: { type: GraphQLInt },
19  },
20});
21
22const TipoFruta = new GraphQLObjectType({
23  name: "Fruta",
24  fields: {
25    id: { type: new GraphQLNonNull(GraphQLID) },
26    nome: { type: new GraphQLNonNull(GraphQLString) },
27    descricao: { type: GraphQLString },
28    ehGostosa: { type: GraphQLBoolean },
29    calorias: { type: GraphQLInt },
30    receitas: {
31      type: new GraphQLList(TipoReceita),
32      resolve(fruta) {
33        return receitas.filter((receita) => receita.frutaId === fruta.id);
34      },
35    },
36  },
37});
38
39const TipoConsulta = new GraphQLObjectType({
40  name: "Query",
41  fields: {
42    frutas: {
43      type: new GraphQLList(TipoFruta),
44      resolve() {
45        return frutas;
46      },
47    },
48    fruta: {
49      type: TipoFruta,
50      args: {
51        id: { type: new GraphQLNonNull(GraphQLID) },
52      },
53      resolve(pai, argumentos) {
54        return frutas.find((fruta) => fruta.id === argumentos.id);
55      },
56    },
57  },
58});
59
60const TipoMutacao = new GraphQLObjectType({
61  name: "Mutation",
62  fields: {
63    adicionarFruta: {
64      type: TipoFruta,
65      args: {
66        nome: { type: new GraphQLNonNull(GraphQLString) },
67        calorias: { type: GraphQLInt },
68      },
69      resolve(pai, argumentos) {
70        const fruta = { id: String(frutas.length + 1), nome: argumentos.nome, calorias: argumentos.calorias, ehGostosa: true };
71        frutas.push(fruta);
72        return fruta;
73      },
74    },
75  },
76});
77
78module.exports = new GraphQLSchema({ query: TipoConsulta, mutation: TipoMutacao });

As ideias novas, uma por uma:

  • GraphQLList(TipoFruta) quer dizer "uma lista de frutas".
  • GraphQLNonNull(…) quer dizer "nunca nulo": toda fruta sempre tem id e nome, e o argumento id é obrigatório.
  • args declara os argumentos que um campo aceita; o resolver os recebe no segundo parâmetro, aqui chamado argumentos.
  • Resolvers aninhados: receitas tem o seu próprio resolver, e o primeiro parâmetro dele é o objeto pai, a fruta sendo resolvida. É assim que o GraphQL caminha de uma fruta até as suas receitas.
  • Mutation guarda os campos que mudam dados (já chegamos lá).

Reinicie o servidor e teste uma lista:

1$ node perguntar.js "{ frutas { nome calorias } }"
2{
3  "data": {
4    "frutas": [
5      {
6        "nome": "Banana",
7        "calorias": 89
8      },
9      {
10        "nome": "Maçã",
11        "calorias": 52
12      },
13      {
14        "nome": "Durião",
15        "calorias": 147
16      }
17    ]
18  }
19}

Busque uma fruta pelo ID e as receitas dela no mesmo pedido:

1$ node perguntar.js '{ fruta(id: "1") { nome receitas { nome minutos } } }'
2{
3  "data": {
4    "fruta": {
5      "nome": "Banana",
6      "receitas": [
7        {
8          "nome": "Pão de banana",
9          "minutos": 70
10        },
11        {
12          "nome": "Vitamina de banana",
13          "minutos": 5
14        }
15      ]
16    }
17  }
18}

(As aspas simples em volta da consulta impedem que o terminal engula as aspas duplas de dentro. No PowerShell, use aspas simples também.)

O schema da API de frutas como um grafo. O tipo Query tem dois campos: frutas, uma lista de Fruta, e fruta(id: ID!), uma única Fruta. O tipo Mutation tem adicionarFruta(nome: String!, calorias: Int), que devolve uma Fruta. O tipo Fruta tem id: ID!, nome: String!, descricao: String, ehGostosa: Boolean, calorias: Int e receitas, uma lista de Receita. O tipo Receita tem nome: String e minutos: Int. Uma consulta é um caminho por este grafo: Query, depois fruta, depois receitas

O schema na linguagem do próprio GraphQL

O nosso schema é montado com objetos JavaScript, mas o GraphQL tem a sua própria linguagem de definição de schema (SDL, Schema Definition Language), bem compacta, e o pacote graphql consegue imprimir o nosso nela:

1// imprimir-schema.js
2const { printSchema } = require("graphql");
3const schema = require("./schema");
4
5console.log(printSchema(schema));
1$ node imprimir-schema.js
2type Query {
3  frutas: [Fruta]
4  fruta(id: ID!): Fruta
5}
6
7type Fruta {
8  id: ID!
9  nome: String!
10  descricao: String
11  ehGostosa: Boolean
12  calorias: Int
13  receitas: [Receita]
14}
15
16type Receita {
17  nome: String
18  minutos: Int
19}
20
21type Mutation {
22  adicionarFruta(nome: String!, calorias: Int): Fruta
23}

[Fruta] é uma lista e ! quer dizer não nulo. É essa a notação que você vai ver em toda documentação de GraphQL, e muitas bibliotecas deixam você escrever o schema assim desde o começo.

Quando algo dá errado

O GraphQL confere cada consulta com o schema antes de rodar qualquer resolver. Peça um campo que não existe:

1$ node perguntar.js "{ frutas { nome cor } }"
2{
3  "errors": [
4    {
5      "message": "Cannot query field \"cor\" on type \"Fruta\".",
6      "locations": [
7        {
8          "line": 1,
9          "column": 17
10        }
11      ]
12    }
13  ]
14}

("Não é possível consultar o campo cor no tipo Fruta.") Esqueça um argumento obrigatório:

1$ node perguntar.js "{ fruta { nome } }"
2{
3  "errors": [
4    {
5      "message": "Field \"fruta\" argument \"id\" of type \"ID!\" is required, but it was not provided.",
6      "locations": [
7        {
8          "line": 1,
9          "column": 3
10        }
11      ]
12    }
13  ]
14}

("O argumento id, do tipo ID!, do campo fruta é obrigatório, mas não foi informado.") A resposta tem uma chave errors em vez de data, com a linha e a coluna do problema. Mas uma pergunta válida sem resposta não é erro: pedir uma fruta que não existe dá null:

1$ node perguntar.js '{ fruta(id: "9") { nome } }'
2{
3  "data": {
4    "fruta": null
5  }
6}

Mudando dados: mutações

Consultas leem; mutações (mutations) escrevem. Elas são declaradas no tipo Mutation e chamadas com a palavra-chave mutation. Assim como uma consulta, uma mutação escolhe quais campos do resultado quer de volta:

1$ node perguntar.js 'mutation { adicionarFruta(nome: "Manga", calorias: 60) { id nome calorias } }'
2{
3  "data": {
4    "adicionarFruta": {
5      "id": "4",
6      "nome": "Manga",
7      "calorias": 60
8    }
9  }
10}

(O nosso "banco de dados" é um array na memória, então a manga some quando o servidor reinicia. Um resolver de verdade a salvaria num banco de dados.)

Variáveis

Aplicativos de verdade não colam valores no texto da consulta. Eles escrevem a consulta uma vez com variáveis, marcadas com $, e mandam os valores separados, em JSON:

1$ node perguntar.js 'query FrutaPorId($id: ID!) { fruta(id: $id) { nome calorias } }' '{"id": "4"}'
2{
3  "data": {
4    "fruta": {
5      "nome": "Manga",
6      "calorias": 60
7    }
8  }
9}

FrutaPorId é o nome da operação (útil nos logs e nas abas do GraphiQL), e $id: ID! declara a variável e o seu tipo. O nosso perguntar.js manda o segundo argumento como as variables do pedido. Manter os valores fora do texto da consulta também é mais seguro, pelo mesmo motivo que você não monta SQL colando strings.

Quanto o GraphQL economiza?

Acrescentamos duas rotas no estilo REST ao mesmo servidor (GET /frutas, devolvendo as frutas completas, e GET /frutas/:id/receitas) e medimos duas telas:

GraphQL vs. REST para duas telas, medido na API de frutas deste artigo com Node.js 26 e Express 5.3. Tela 1, uma lista de nomes de frutas: o REST faz 1 pedido e baixa 293 bytes; o GraphQL faz 1 pedido e baixa 76 bytes, 74% menos. Tela 2, nomes das frutas com os nomes das receitas: o REST faz 4 pedidos, um para as frutas e mais um por fruta, e baixa 460 bytes; o GraphQL faz 1 pedido e baixa 199 bytes, 57% menos. O número de pedidos REST cresce com o número de frutas: 1 + N. O GraphQL fica em 1. Em APIs reais, cujos registros costumam ter dezenas de campos, a economia costuma ser maior
TelaPedidos RESTBytes RESTPedidos GraphQLBytes GraphQL
Nomes das frutas1293176
Nomes das frutas com os nomes das receitas44601199

Para a lista de nomes, o GraphQL mandou cerca de um quarto dos bytes. Para os nomes com receitas, o REST precisou de um pedido por fruta além da lista (1 + N, o famoso padrão "N+1"), enquanto o GraphQL precisou de um. Num celular, cada ida e volta extra pode somar dezenas ou centenas de milissegundos, então essa diferença costuma pesar mais que os bytes.

Sua vez: pratique

Tente fazer estes antes de espiar as respostas. Use a API completa da seção "Uma API de verdade", recém-reiniciada.

Exercício 1: Adivinhe a resposta (aquecimento). O que esta consulta devolve?

{ fruta(id: "2") { nome ehGostosa } }

Exercício 2: Acrescente um campo. Dê a cada fruta uma cor (amarela, vermelha e verde) e deixe que ela seja consultada, para que { frutas { nome cor } } funcione.

Exercício 3: Uma lista vazia. Escreva uma consulta pedindo as calorias do durião e os nomes das receitas dele. O que você espera em receitas?

Exercício 4: Ache o erro. Por que esta consulta é recusada?

{ frutas }

Exercício 5: Filtre com um argumento (desafio). Acrescente um argumento opcional maxCalorias ao campo frutas, para que { frutas(maxCalorias: 100) { nome calorias } } devolva só as frutas com no máximo 100 calorias, enquanto { frutas { nome } } continua devolvendo todas.

Quiz rápido

  1. Quantos endpoints uma API GraphQL típica tem?
  2. Qual é a diferença entre o schema e os resolvers?
  3. O que o ! quer dizer em ID!?
  4. Qual é o primeiro parâmetro de um resolver aninhado como o de receitas?
  5. Quando usar uma mutação em vez de uma consulta?

Respostas

Exercício 1: Só os dois campos pedidos, da fruta com ID 2:

1$ node perguntar.js '{ fruta(id: "2") { nome ehGostosa } }'
2{
3  "data": {
4    "fruta": {
5      "nome": "Maçã",
6      "ehGostosa": true
7    }
8  }
9}

Exercício 2: Acrescente o dado no dados.js e uma linha no TipoFruta. O resolver padrão lê a cor direto de cada objeto:

  { id: "1", nome: "Banana", descricao: "Doce e fácil de descascar", ehGostosa: true, calorias: 89, cor: "amarela" },
  { id: "2", nome: "Maçã", descricao: "Crocante e suculenta", ehGostosa: true, calorias: 52, cor: "vermelha" },
  { id: "3", nome: "Durião", descricao: "Famoso pelo cheiro forte", ehGostosa: false, calorias: 147, cor: "verde" },
    calorias: { type: GraphQLInt },
    cor: { type: GraphQLString },
1$ node perguntar.js "{ frutas { nome cor } }"
2{
3  "data": {
4    "frutas": [
5      {
6        "nome": "Banana",
7        "cor": "amarela"
8      },
9      {
10        "nome": "Maçã",
11        "cor": "vermelha"
12      },
13      {
14        "nome": "Durião",
15        "cor": "verde"
16      }
17    ]
18  }
19}

Exercício 3: Nenhuma receita tem o ID do durião, então o filter devolve um array vazio, e o GraphQL manda uma lista vazia (não null, e não um erro):

1$ node perguntar.js '{ fruta(id: "3") { nome calorias receitas { nome } } }'
2{
3  "data": {
4    "fruta": {
5      "nome": "Durião",
6      "calorias": 147,
7      "receitas": []
8    }
9  }
10}

Exercício 4: frutas é uma lista de objetos, e o GraphQL nunca adivinha quais campos você quer: você tem que listar pelo menos um.

1$ node perguntar.js "{ frutas }"
2{
3  "errors": [
4    {
5      "message": "Field \"frutas\" of type \"[Fruta]\" must have a selection of subfields. Did you mean \"frutas { ... }\"?",
6      "locations": [
7        {
8          "line": 1,
9          "column": 3
10        }
11      ]
12    }
13  ]
14}

("O campo frutas, do tipo [Fruta], precisa de uma seleção de subcampos.")

Exercício 5: Declare o argumento sem GraphQLNonNull para ele ser opcional, e só filtre quando ele vier:

1    frutas: {
2      type: new GraphQLList(TipoFruta),
3      args: {
4        maxCalorias: { type: GraphQLInt },
5      },
6      resolve(pai, argumentos) {
7        if (argumentos.maxCalorias === undefined) return frutas;
8        return frutas.filter((fruta) => fruta.calorias <= argumentos.maxCalorias);
9      },
10    },
1$ node perguntar.js "{ frutas(maxCalorias: 100) { nome calorias } }"
2{
3  "data": {
4    "frutas": [
5      {
6        "nome": "Banana",
7        "calorias": 89
8      },
9      {
10        "nome": "Maçã",
11        "calorias": 52
12      }
13    ]
14  }
15}
1$ node perguntar.js "{ frutas { nome } }"
2{
3  "data": {
4    "frutas": [
5      {
6        "nome": "Banana"
7      },
8      {
9        "nome": "Maçã"
10      },
11      {
12        "nome": "Durião"
13      }
14    ]
15  }
16}

Quiz: (1) Um, normalmente /graphql. (2) O schema descreve o que pode ser pedido e os tipos; os resolvers são as funções que produzem o valor de cada campo. (3) Não nulo: o valor sempre está presente (num argumento, quer dizer obrigatório). (4) O objeto pai, aqui a fruta cujas receitas estão sendo resolvidas. (5) Quando a operação muda dados (criar, atualizar ou apagar); consultas só leem.

Cola rápida

Eu quero…Escreva
Instalar os pacotesnpm install express graphql graphql-http ruru
Ligar o GraphQL no Expressapp.all("/graphql", createHandler({ schema }))
Servir o playground GraphiQLresposta.end(ruruHTML({ endpoint: "/graphql" }))
Definir um tipo de objetonew GraphQLObjectType({ name: "Fruta", fields: { … } })
Uma lista / um valor obrigatórionew GraphQLList(TipoFruta) / new GraphQLNonNull(GraphQLID)
Aceitar argumentosargs: { id: { type: new GraphQLNonNull(GraphQLID) } }
Resolver um camporesolve(pai, argumentos) { return … }
Consultar alguns campos{ frutas { nome calorias } }
Passar um argumento{ fruta(id: "1") { nome } }
Mudar dadosmutation { adicionarFruta(nome: "Manga") { id } }
Usar variáveisquery FrutaPorId($id: ID!) { fruta(id: $id) { nome } } mais {"id": "1"}
Ver o schema em SDLprintSchema(schema)

Resumindo

O GraphQL deixa cada cliente pedir exatamente os dados de que precisa, num pedido só:

  • Um endpoint, muitos formatos: a consulta do cliente decide quais campos voltam, e a resposta espelha a consulta.
  • O schema é o contrato: tipos, listas ([Fruta]), valores não nulos (!) e argumentos, tudo conferido antes de qualquer coisa rodar.
  • Os resolvers buscam os dados, campo por campo; resolvers aninhados recebem o objeto pai.
  • Mutações mudam dados, e as variáveis deixam os valores fora do texto da consulta.
  • O GraphiQL dá autocompletar e documentação ao vivo de graça.
  • A economia pode ser grande: nas nossas medições, um quarto dos bytes numa tela de lista, e 1 pedido em vez de 1 + N para dados aninhados.
  • Use o graphql-http, não o obsoleto express-graphql.

Novo na linguagem por trás de tudo isso? Leia O Que É a Linguagem de Programação JavaScript?.

Obrigado pela leitura!

Conhece alguém que vai gostar? Mande para essa pessoa.

Mais artigos

Leia a seguir

Comentários

Seja gentil e específico. Comentários com links de contas novas aparecem depois da análise de um moderador.