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
| Aspecto | REST | GraphQL |
|---|---|---|
| Numero de requisicoes | Tende a ser maior | Pode ser minimizado |
| Tamanho do payload | Over-fetching | Otimizado |
| Cache HTTP | Suporte nativo | Requer implementacao adicional |
| Cache CDN | Facil | Requer adaptacao |
| Problema N+1 (servidor) | Nao existe | Resolver 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
- Analise os requisitos: Complexidade dos dados, diversidade de clientes
- Habilidades da equipe: Experiencia existente e custo de aprendizado
- Requisitos de performance: Cache, largura de banda, latencia
- 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.
Links de Referencia
- Documentacao Oficial GraphQL
- Diretrizes de Design REST API
- Documentacao Apollo Server
- Especificacao OpenAPI