GraphQL vs REST API - Filosofias de Design e Criterios de Escolha

16 min leitura | 2025.12.02

Em design de API, “GraphQL ou REST, qual escolher?” e um tema frequentemente debatido. Ambos possuem filosofias de design fundamentalmente diferentes, e nao se trata de qual e superior, mas de fazer a escolha apropriada de acordo com os requisitos do projeto. Este artigo visa fornecer uma compreensao profunda das diferencas entre ambos para que voce possa tomar a decisao correta.

Diferenca nas Filosofias de Design

REST: Arquitetura Orientada a Recursos

REST e uma filosofia de design centrada em “recursos”.

flowchart TB
    subgraph REST["Principios de Design REST"]
        URI["/users/123<br/>URI que identifica univocamente o recurso User"]

        subgraph Methods["Metodos HTTP expressam acoes"]
            GET["GET /users/123 → Obter usuario"]
            PUT["PUT /users/123 → Atualizar usuario"]
            DELETE["DELETE /users/123 → Deletar usuario"]
            POST["POST /users → Criar usuario"]
        end
    end

GraphQL: Abordagem de Linguagem de Consulta

GraphQL e uma filosofia de design onde “o cliente obtem declarativamente os dados que precisa”.

flowchart TB
    subgraph GraphQL["Principios de Design GraphQL"]
        Endpoint["/graphql<br/>Endpoint unico"]

        subgraph Query["Especifica os dados necessarios na query"]
            Q1["user(id: '123')"]
            Q2["→ name"]
            Q3["→ email"]
            Q4["→ posts(limit: 5)"]
            Q5["    → title"]
        end

        Endpoint --> Query
    end

Comparacao de Padroes de Obtencao de Dados

Problema de Over-fetching

// REST: Retorna dados desnecessarios tambem
// GET /users/123
{
  "id": "123",
  "name": "Joao Silva",
  "email": "joao@example.com",
  "phone": "11-91234-5678",        // desnecessario
  "address": "Sao Paulo...",       // desnecessario
  "createdAt": "2024-01-01",       // desnecessario
  "updatedAt": "2024-12-01",       // desnecessario
  "settings": { ... },             // desnecessario
  "profile": { ... }               // desnecessario
}

// GraphQL: Obtem apenas os campos necessarios
// query { user(id: "123") { name, email } }
{
  "data": {
    "user": {
      "name": "Joao Silva",
      "email": "joao@example.com"
    }
  }
}

Problema de Under-fetching (Requisicoes N+1)

// REST: Multiplas requisicoes necessarias
// 1. GET /users/123
// 2. GET /users/123/posts
// 3. GET /posts/1/comments
// 4. GET /posts/2/comments
// ... (problema N+1)

// GraphQL: Obtem todos os dados em 1 requisicao
query {
  user(id: "123") {
    name
    posts {
      title
      comments {
        content
        author { name }
      }
    }
  }
}

Definicao de Schema

REST: OpenAPI/Swagger

# openapi.yaml
openapi: 3.0.0
info:
  title: User API
  version: 1.0.0

paths:
  /users/{id}:
    get:
      summary: Obter usuario
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Sucesso
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string
          format: email

GraphQL: SDL (Schema Definition Language)

# schema.graphql
type User {
  id: ID!
  name: String!
  email: String!
  posts(limit: Int, offset: Int): [Post!]!
  createdAt: DateTime!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments: [Comment!]!
}

type Comment {
  id: ID!
  content: String!
  author: User!
}

type Query {
  user(id: ID!): User
  users(filter: UserFilter): [User!]!
  post(id: ID!): Post
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
}

input CreateUserInput {
  name: String!
  email: String!
}

Comparacao de Implementacao

Servidor REST (Express)

// REST API (Express + TypeScript)
import express from 'express';

const app = express();
app.use(express.json());

// Obter usuario
app.get('/users/:id', async (req, res) => {
  const user = await db.user.findUnique({
    where: { id: req.params.id }
  });

  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }

  res.json(user);
});

// Obter posts do usuario
app.get('/users/:id/posts', async (req, res) => {
  const posts = await db.post.findMany({
    where: { authorId: req.params.id },
    take: parseInt(req.query.limit as string) || 10,
    skip: parseInt(req.query.offset as string) || 0
  });

  res.json(posts);
});

// Criar usuario
app.post('/users', async (req, res) => {
  const { name, email } = req.body;

  const user = await db.user.create({
    data: { name, email }
  });

  res.status(201).json(user);
});

Servidor GraphQL (Apollo Server)

// GraphQL API (Apollo Server + TypeScript)
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const typeDefs = `#graphql
  type User {
    id: ID!
    name: String!
    email: String!
    posts(limit: Int, offset: Int): [Post!]!
  }

  type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
  }

  type Query {
    user(id: ID!): User
    users: [User!]!
  }

  type Mutation {
    createUser(name: String!, email: String!): User!
  }
`;

const resolvers = {
  Query: {
    user: async (_, { id }) => {
      return db.user.findUnique({ where: { id } });
    },
    users: async () => {
      return db.user.findMany();
    }
  },

  Mutation: {
    createUser: async (_, { name, email }) => {
      return db.user.create({ data: { name, email } });
    }
  },

  // Resolver em nivel de campo
  User: {
    posts: async (parent, { limit = 10, offset = 0 }) => {
      return db.post.findMany({
        where: { authorId: parent.id },
        take: limit,
        skip: offset
      });
    }
  }
};

const server = new ApolloServer({ typeDefs, resolvers });

Estrategia de Cache

REST: Utilizacao de Cache HTTP

// REST: Cache HTTP padrao
app.get('/users/:id', async (req, res) => {
  const user = await getUser(req.params.id);

  res
    .set('Cache-Control', 'public, max-age=300')  // Cache de 5 minutos
    .set('ETag', `"${user.version}"`)
    .json(user);
});

// Lado do cliente
fetch('/users/123', {
  headers: {
    'If-None-Match': '"v1"'  // Requisicao condicional
  }
});
// → 304 Not Modified (cache valido)

GraphQL: Cache no Lado do Cliente

// GraphQL: Cache normalizado do Apollo Client
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: '/graphql',
  cache: new InMemoryCache({
    typePolicies: {
      User: {
        keyFields: ['id'],  // Chave do cache
      },
      Post: {
        keyFields: ['id'],
      }
    }
  })
});

// Cache e automaticamente normalizado
// User:123 → { name: "...", email: "..." }
// Post:456 → { title: "...", author: { __ref: "User:123" } }

Tratamento de Erros

REST: Codigos de Status HTTP

// REST: Codigos de status HTTP padrao
app.get('/users/:id', async (req, res) => {
  try {
    const user = await getUser(req.params.id);

    if (!user) {
      return res.status(404).json({
        error: 'NOT_FOUND',
        message: 'Usuario nao encontrado'
      });
    }

    res.json(user);
  } catch (error) {
    res.status(500).json({
      error: 'INTERNAL_ERROR',
      message: 'Ocorreu um erro no servidor'
    });
  }
});

// Exemplo de resposta
// HTTP 404
{
  "error": "NOT_FOUND",
  "message": "Usuario nao encontrado"
}

GraphQL: Array de Errors

// GraphQL: Erros parciais sao possiveis
const resolvers = {
  Query: {
    user: async (_, { id }) => {
      const user = await getUser(id);
      if (!user) {
        throw new GraphQLError('Usuario nao encontrado', {
          extensions: {
            code: 'NOT_FOUND',
            argumentName: 'id'
          }
        });
      }
      return user;
    }
  }
};

// Exemplo de resposta (sucesso parcial)
{
  "data": {
    "user": null,
    "posts": [...]  // Outros campos com sucesso
  },
  "errors": [
    {
      "message": "Usuario nao encontrado",
      "path": ["user"],
      "extensions": {
        "code": "NOT_FOUND"
      }
    }
  ]
}

Caracteristicas de Performance

Tabela Comparativa

AspectoRESTGraphQL
Numero de requisicoesTende a ser maiorPode ser minimizado
Tamanho do payloadOver-fetchingOtimizado
Cache HTTPSuporte nativoRequer implementacao adicional
Cache CDNFacilRequer adaptacao
Problema N+1 (servidor)Nao existeResolver com DataLoader

Solucao para o Problema N+1 no GraphQL

// Otimizacao com DataLoader
import DataLoader from 'dataloader';

// Definicao do batch loader
const userLoader = new DataLoader(async (userIds: string[]) => {
  const users = await db.user.findMany({
    where: { id: { in: userIds } }
  });

  // Retorna mantendo a ordem dos IDs
  const userMap = new Map(users.map(u => [u.id, u]));
  return userIds.map(id => userMap.get(id));
});

// Uso no resolver
const resolvers = {
  Post: {
    author: (parent, _, context) => {
      return context.userLoader.load(parent.authorId);
    }
  }
};

// Resultado: Query em batch em vez de queries individuais
// SELECT * FROM users WHERE id IN ('1', '2', '3', ...)

Consideracoes de Seguranca

REST: Controle por Endpoint

// REST: Autorizacao por rota
app.get('/admin/users', requireAdmin, async (req, res) => {
  // Acessivel apenas para administradores
});

app.get('/users/:id', async (req, res) => {
  // Endpoint publico
});

GraphQL: Controle em Nivel de Campo

// GraphQL: Controle via diretivas
const typeDefs = `#graphql
  directive @auth(requires: Role!) on FIELD_DEFINITION

  type User {
    id: ID!
    name: String!
    email: String! @auth(requires: OWNER)
    salary: Int @auth(requires: ADMIN)
  }
`;

// Limitacao de complexidade de query
import { createComplexityLimitRule } from 'graphql-validation-complexity';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    createComplexityLimitRule(1000)  // Limite de complexidade
  ]
});

Medidas de Seguranca Especificas do GraphQL

// 1. Limitacao de profundidade da query
import depthLimit from 'graphql-depth-limit';

const server = new ApolloServer({
  validationRules: [depthLimit(10)]  // Profundidade maxima 10
});

// 2. Desabilitar introspection (ambiente de producao)
const server = new ApolloServer({
  introspection: process.env.NODE_ENV !== 'production'
});

// 3. Queries persistentes
const server = new ApolloServer({
  persistedQueries: {
    cache: new RedisCache()
  }
});

Criterios de Escolha

Casos Onde REST e Mais Adequado

Situacoes para escolher REST:

✓ Operacoes CRUD simples sao o foco
✓ Deseja maximizar o uso de cache HTTP
✓ Cache CDN/Edge e importante
✓ Equipe tem experiencia com REST
✓ Fornecendo como API publica
✓ Muito upload/download de arquivos

Casos Onde GraphQL e Mais Adequado

Situacoes para escolher GraphQL:

✓ Requisitos de dados complexos (muitos relacionamentos)
✓ Multiplos clientes (Web, Mobile, etc.)
✓ Desenvolvimento liderado pelo cliente (frontend primeiro)
✓ Funcionalidades em tempo real (Subscriptions)
✓ BFF de microsservicos (Backend for Frontend)
✓ Otimizacao de largura de banda e importante (mobile)

Abordagem Hibrida

// Padrao usando ambos
const app = express();

// REST: Upload de arquivos
app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ url: req.file.path });
});

// REST: Health check
app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

// GraphQL: Obtencao/atualizacao de dados
app.use('/graphql', apolloMiddleware);

Resumo

GraphQL e REST sao paradigmas de design de API com diferentes pontos fortes.

Pontos Fortes do REST

  • Maximiza os recursos HTTP
  • Simples e facil de entender
  • Ecossistema de ferramentas rico
  • Cache facil

Pontos Fortes do GraphQL

  • Obtencao de dados flexivel
  • Schema com tipagem segura
  • Obtencao de dados complexos em uma unica requisicao
  • Melhora a experiencia do desenvolvedor

Diretrizes de Escolha

  1. Analise os requisitos: Complexidade dos dados, diversidade de clientes
  2. Habilidades da equipe: Experiencia existente e custo de aprendizado
  3. Requisitos de performance: Cache, largura de banda, latencia
  4. Extensibilidade futura: Escalabilidade, manutencao

Qualquer escolha pode construir uma excelente API quando projetada e implementada adequadamente. O importante e fazer a escolha que se adequa as caracteristicas do seu projeto.

← Voltar para a lista