Felipe Moacir

GraphQL vs REST: Quando Usar Cada Um

GraphQLRESTAPIs
GraphQL vs REST: Quando Usar Cada Um

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.