← Stack web // stack web · back end · api

GraphQL: peça só os campos que quiser

Numa API comum, quem escreve o servidor decide o que vem na resposta. Em GraphQL, quem decide é quem faz o pedido. O servidor publica a lista do que existe, e cada cliente monta a resposta que precisa naquela tela.

O que GraphQL resolve

Uma tela de aplicativo mostra a foto e o nome de um livro. A rota que devolve esse livro manda quarenta campos, porque foi desenhada para a tela de detalhe. Trinta e oito atravessam a rede de graça, no 4G do usuário, toda vez que a lista carrega.

O outro lado do mesmo problema aparece na tela seguinte. Falta um dado, a rota não tem ele, e o app dispara três chamadas em sequência para montar uma coisa só. Os dois nomes dessa dor são over-fetching e under-fetching, e a resposta do GraphQL para os dois é a mesma: mover a escolha dos campos do servidor para o pedido.

Os conceitos que você precisa antes da primeira linha

  • Schema. O contrato do servidor, escrito numa linguagem própria chamada SDL. Ele declara quais tipos existem, quais campos cada tipo tem e de que tipo é cada campo. Nada que esteja fora do schema pode ser pedido, e o servidor recusa antes de executar qualquer coisa.
  • Tipo e campo. Um tipo agrupa campos que andam juntos, como Livro com titulo, autor e ano. Campos apontam para outros tipos, e é assim que o schema vira um grafo em vez de uma lista de rotas.
  • Query. A operação de leitura. Você escreve o formato que quer e o servidor devolve JSON com exatamente esse formato, campo por campo, na mesma hierarquia. Escrever a consulta já é escrever o desenho da resposta.
  • Resolver. A função que sabe onde buscar o valor de um campo. Um resolver lê do banco, outro chama uma API de terceiro, outro devolve uma constante. O GraphQL só chama os resolvers dos campos que apareceram no pedido.
  • Um endereço só. Um servidor GraphQL costuma expor uma única rota, quase sempre /graphql, e recebe tudo por POST. O que muda entre um pedido e outro é o corpo da requisição, nunca a URL.

O contrato por dentro: schema, tipos e a consulta que escolhe campos

GraphQL troca os vários endereços do REST por um endpoint só, e move o contrato pro schema: uma declaração tipada de tudo que a API sabe responder. É tipagem estática aplicada à API: o cliente sabe de antemão que campos existem e de que tipo, e uma consulta pedindo campo inexistente é recusada antes de tocar em banco.

  • O schema define tipos com campos: type Cliente { id: ID!, nome: String!, pedidos: [Pedido!]! }. A exclamação é "nunca nulo", os colchetes são lista. Os tipos base (Int, Float, String, Boolean, ID) são os scalars.
  • Query lê, mutation escreve: a consulta tem a forma do dado que volta, e o cliente pede só os campos que quer, inclusive atravessando relações num pedido só. É a resposta ao under/overfetching que a seção anterior descreveu.
  • Resolver é quem busca de verdade: cada campo do schema tem uma função no servidor que sabe encontrá-lo (no banco, num serviço, onde for). O GraphQL é a fachada tipada, e o dado continua vindo de onde sempre veio.
# a consulta tem a forma da resposta
query {
  cliente(id: 42) {
    nome
    pedidos {
      total
    }
  }
}

# resposta: só o que foi pedido
{ "data": { "cliente": {
    "nome": "Ana",
    "pedidos": [ { "total": 150.0 } ] } } }

Compare com o REST da página anterior: lá o servidor decide a forma da resposta, aqui o cliente desenha. Nenhum dos dois é upgrade do outro: são contratos diferentes pra necessidades diferentes.

Hello world: uma API GraphQL em seis passos

O caminho abaixo é o da documentação oficial do GraphQL.js, com Express e o pacote graphql-http. São três dependências e um arquivo.

  1. Abra o terminal e confira se o Node.js está na máquina:

    node --version
    npm --version

    Se o terminal responder que o comando não existe, instale a versão LTS antes de seguir. Veja a página de Node.js para o passo a passo.

  2. Crie a pasta do projeto e prepare o package.json:

    mkdir hello-graphql
    cd hello-graphql
    npm init -y
    npm pkg set type=module

    A última linha marca o projeto como módulo ES, que é o que permite escrever import em vez de require no arquivo do servidor.

  3. Instale as três dependências:

    npm install express graphql-http graphql --save

    Esse é o comando da documentação oficial. O graphql é o motor que interpreta e executa a consulta, o graphql-http traduz requisição HTTP para chamada do motor, e o express é o servidor web que atende a porta.

  4. Crie um arquivo server.js na pasta, com este conteúdo:

    import { buildSchema } from 'graphql';
    import { createHandler } from 'graphql-http/lib/use/express';
    import express from 'express';
    
    // O schema declara os campos que existem. O cliente escolhe quais quer.
    const schema = buildSchema(`
      type Livro {
        id: ID
        titulo: String
        autor: String
        ano: Int
        paginas: Int
        sinopse: String
      }
    
      type Query {
        livro: Livro
      }
    `);
    
    // O resolver devolve o objeto inteiro. Quem corta é o GraphQL.
    const root = {
      livro() {
        return {
          id: '1',
          titulo: 'Dom Casmurro',
          autor: 'Machado de Assis',
          ano: 1899,
          paginas: 256,
          sinopse: 'Bentinho conta a própria vida e tenta provar uma suspeita que nunca sai do lugar.',
        };
      },
    };
    
    const app = express();
    
    app.all('/graphql', createHandler({ schema: schema, rootValue: root }));
    
    app.listen(4000);
    console.log('Servidor GraphQL no ar em http://localhost:4000/graphql');

    Repare no root. Ele devolve os seis campos sempre, sem saber o que foi pedido. Quem recorta a resposta é o motor do GraphQL, depois, comparando o objeto com a consulta que chegou.

  5. Suba o servidor:

    node server.js

    Deixe esse terminal aberto. Para encerrar depois, aperte Ctrl + C.

  6. Abra um segundo terminal e envie as duas consultas. Primeiro pedindo um campo:

    curl -X POST http://localhost:4000/graphql \
      -H 'Content-Type: application/json' \
      -d '{"query":"{ livro { titulo } }"}'

    Agora pedindo os seis, para o mesmo endereço:

    curl -X POST http://localhost:4000/graphql \
      -H 'Content-Type: application/json' \
      -d '{"query":"{ livro { id titulo autor ano paginas sinopse } }"}'

A prova de que funcionou. As duas respostas têm tamanhos diferentes, e a URL foi a mesma nas duas vezes. A primeira volta assim:

{"data":{"livro":{"titulo":"Dom Casmurro"}}}

E a segunda volta assim:

{"data":{"livro":{"id":"1","titulo":"Dom Casmurro","autor":"Machado de Assis","ano":1899,"paginas":256,"sinopse":"Bentinho conta a própria vida e tenta provar uma suspeita que nunca sai do lugar."}}}

Aqui foram 44 bytes contra 200 bytes no mesmo POST /graphql, no mesmo servidor, com o mesmo resolver rodando. Nenhuma rota nova foi criada para a versão curta. É esse o ponto inteiro do GraphQL, e ele cabe nessas duas linhas de terminal.

Para ver o contrato agindo, peça um campo que não existe no schema:

curl -X POST http://localhost:4000/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ livro { editora } }"}'

A resposta é Cannot query field "editora" on type "Livro", com a linha e a coluna do erro. O servidor recusou antes de chamar o resolver. Se o terminal do servidor reclamar de EADDRINUSE, outro programa ocupa a porta 4000. Troque o 4000 por 4001 no arquivo e nos comandos curl.

O que GraphQL não é

A confusão mais comum é achar que GraphQL fala com o banco de dados. O nome tem "QL" de query language, a consulta parece SQL de longe, e a conclusão vem sozinha: bastaria apontar o GraphQL para o banco e ele resolveria o resto.

Não funciona assim. GraphQL é um protocolo entre cliente e servidor, e a documentação oficial responde direto à pergunta de sempre sobre substituir ORM: a especificação é agnóstica à fonte de dados e não implementa mapeamento objeto-relacional. Quem busca o dado é o resolver que você escreve, e ele pode ler de PostgreSQL, de um arquivo, de outra API ou de uma constante, como no exemplo acima.

A consequência prática aparece no primeiro projeto real. Se um campo do schema for lento, o problema está no resolver dele, não na consulta que o cliente escreveu. E a consulta que pede uma lista com um campo aninhado dispara um resolver por item, o famoso N+1, que continua sendo trabalho de quem escreve o servidor.

Fontes

  • GraphQL, Introduction to GraphQL, a introdução oficial ao schema, às queries e aos resolvers. Conferida em 15/08/2026.
  • GraphQL.js, Running an Express GraphQL Server, de onde saíram o comando npm install express graphql-http graphql --save, o uso de buildSchema e o esqueleto do server.js com app.all('/graphql', createHandler(...)) na porta 4000. Conferida em 15/08/2026.
  • GraphQL.js, graphql-http, a página que documenta o createHandler e o import de graphql-http/lib/use/express. Conferida em 15/08/2026.
  • GraphQL FAQ, General, onde está a resposta "Does GraphQL replace ORMs? No", usada na seção sobre banco de dados. Conferida em 15/08/2026.

O hello world desta página foi executado em 15/08/2026, numa pasta vazia, com Node.js v22.23.0 e npm 10.9.8. O npm install trouxe express 5.2.1, graphql-http 1.23.0 e graphql 17.0.2. O servidor subiu na porta 4000 e imprimiu a linha do console.log. A consulta de um campo devolveu 44 bytes e a de seis campos devolveu 200 bytes, os dois JSON exatamente como estão transcritos acima. A consulta com o campo editora devolveu a mensagem de erro citada.

Para continuar

O próximo passo é REST, que resolve o mesmo problema por outro caminho, com uma rota por recurso e o significado carregado pelo verbo e pelo código de status. O servidor deste tutorial rodou sobre Node.js, e entender o que acontece embaixo do Express ajuda quando a porta não abre. O mapa tem o resto da stack.

O curso desta trilha está em preparação. A lista paga menos.

Os tutoriais daqui continuam gratuitos. O que está a caminho é um curso pago que transforma esse mapa em formação guiada, na ordem certa e com projeto. Deixe seu e-mail: você recebe os estudos novos gratuitos e, no lançamento, o desconto de quem entrou na lista primeiro.