GraphQL promete resolver todos os problemas do REST. Mas adiciona complexidade. Quando vale a pena?
O Problema do REST
Over-fetching
Você quer só o nome do usuário:
GET /api/users/123
Resposta:
{
"id": 123,
"name": "Felipe Moacir",
"email": "felipe@example.com",
"avatar": "https://...",
"bio": "...",
"createdAt": "...",
"followers": 1532,
"following": 423
}
Você recebeu 8 campos, mas queria 1.
Under-fetching
Para exibir um post + autor + comentários, você precisa de 3 requests:
GET /api/posts/456 # Post
GET /api/users/123 # Autor
GET /api/posts/456/comments # Comentários
Waterfall: cada request espera a anterior.
GraphQL: Query Exata
Uma query, dados exatos:
query {
post(id: 456) {
title
author {
name
avatar
}
comments {
text
author { name }
}
}
}
Resposta:
{
"data": {
"post": {
"title": "GraphQL vs REST",
"author": { "name": "Felipe", "avatar": "..." },
"comments": [
{ "text": "Ótimo post!", "author": { "name": "João" } }
]
}
}
}
1 request, dados exatos.
Implementação: Apollo Server
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
const typeDefs = `
type User {
id: ID!
name: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
author: User!
}
type Query {
user(id: ID!): User
post(id: ID!): Post
}
`;
const resolvers = {
Query: {
user: (_, { id }) => db.users.findUnique({ where: { id } }),
post: (_, { id }) => db.posts.findUnique({ where: { id } }),
},
User: {
posts: (user) => db.posts.findMany({ where: { authorId: user.id } }),
},
Post: {
author: (post) => db.users.findUnique({ where: { id: post.authorId } }),
},
};
const server = new ApolloServer({ typeDefs, resolvers });
await startStandaloneServer(server, { port: 4000 });
Problema: N+1 Queries
Query:
query {
posts {
title
author { name }
}
}
Resolvers executam:
SELECT * FROM posts; -- 1 query
SELECT * FROM users WHERE id = 1; -- +1 query por post
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
...
Com 100 posts = 101 queries.
Solução: DataLoader
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (ids) => {
const users = await db.users.findMany({ where: { id: { in: ids } } });
return ids.map(id => users.find(u => u.id === id));
});
const resolvers = {
Post: {
author: (post) => userLoader.load(post.authorId), // Batched!
},
};
Agora: 2 queries (posts + users em batch).
REST Ainda É Válido
Para APIs simples, REST é suficiente:
GET /api/users/:id
POST /api/posts
PUT /api/posts/:id
DELETE /api/posts/:id
Vantagens do REST:
✅ Simples (sem schema, resolvers)
✅ Cache HTTP nativo (CDN, browser)
✅ Melhor para file uploads
✅ Ferramentas maduras (Postman, curl)
GraphQL Vale a Pena Quando:
✅ Frontend precisa de flexibilidade (mobile + web diferentes)
✅ Múltiplas entidades relacionadas (e-commerce, social)
✅ Evitar over-fetching (mobile 3G)
✅ Backend centralizado para múltiplos clientes
REST Vale a Pena Quando:
✅ API simples (CRUD básico)
✅ Cache HTTP importante
✅ Público externo (REST é universal)
✅ Time pequeno (GraphQL adiciona complexidade)
tRPC: Alternativa Type-Safe
Se seu frontend e backend são TypeScript, considere tRPC:
// server.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
export const appRouter = t.router({
getUser: t.procedure
.input((id: string) => id)
.query(async ({ input }) => {
return db.users.findUnique({ where: { id: input } });
}),
});
export type AppRouter = typeof appRouter;
// client.ts
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';
const client = createTRPCProxyClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:3000' })],
});
const user = await client.getUser.query('123'); // Tipado!
Zero codegen, type-safe, RPC.
Performance: Benchmarks
Buscar 100 posts + autores:
- REST (3 requests waterfall): 450ms
- GraphQL (1 request, N+1): 2300ms
- GraphQL (DataLoader): 180ms
- tRPC: 160ms
GraphQL com optimizações é o mais rápido.
Conclusão: GraphQL não é bala de prata. Para APIs simples, REST é suficiente. Para apps complexos, GraphQL + DataLoader vale cada linha de código extra.