TG
rpc·graphql·rest·8 min de leitura

RPC, REST e GraphQL: a mesma chamada remota com interfaces diferentes

Num app fullstack web em TypeScript, REST, GraphQL e RPC/tRPC parecem três mundos. Mas os três serializam dados, mandam por HTTP e voltam com a resposta. Vivem todos na camada 7 da OSI. O que muda não é a remotidade, é a interface mental do topo, e o preço de cada abstração. Com o contraste tRPC vs Server Action no mesmo create.

Read in English
RPC, REST e GraphQL: a mesma chamada remota com interfaces diferentes

Toda discussão sobre "REST vs GraphQL vs RPC" começa errada, como se fossem três tecnologias rivais disputando quem é mais moderno. Não são. Os três fazem exatamente a mesma coisa por baixo: serializam dados, mandam por HTTP pela rede e recebem uma resposta. Nenhum é "mais remoto" que o outro.

O navegador não roda código de servidor. Sem acesso ao banco, sem chaves secretas. O trabalho real acontece no servidor, e qualquer uma das três interfaces existe pra fazer essa ponte. A diferença está só na interface mental do topo, e no preço que cada abstração cobra.

Esse texto nasceu de uma sequência de perguntas reais sobre um app fullstack TypeScript (Next.js + tRPC), enquanto eu desenhava a fronteira entre cliente e servidor. Vou seguir a mesma trilha.

RPC: a abstração que esconde o fetch

RPC quer dizer Remote Procedure Call, chamada de procedimento remoto. A ideia é chamar uma função que roda em outra máquina como se fosse local.

// SEM RPC: encanamento na mão, retorno sem tipo
const res = await fetch("/api/orders", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ eventId, qty }),
});
const order = await res.json(); // tipo "any"
 
// COM RPC (tRPC): parece função local, retorno tipado
const order = await api.order.create.mutate({ eventId, qty });

O RPC só escondeu o fetch, o JSON.stringify e o parse. Por baixo, continua HTTP. Essa é a chave pra entender o resto: a chamada remota não some, ela só ganha uma roupa mais confortável.

Os três, lado a lado: muda a interface, não o transporte

REST, GraphQL e RPC/tRPC são todos chamada remota sobre HTTP. O que muda é como você pensa a chamada:

Como você pensaPor baixo
RESTrecursos + verbos (POST /orders)HTTP
GraphQLquery num grafo ({ order { id } })HTTP (1 endpoint)
RPC/tRPCchamar função (order.create())HTTP

REST pensa em substantivos e verbos: você atua sobre recursos (/orders, /users) com métodos (GET, POST, DELETE). GraphQL pensa em grafo: um endpoint só, e você descreve numa query exatamente os campos que quer, evitando over-fetching. RPC pensa em verbo direto: você chama a ação (order.create) e o transporte some da sua cabeça.

São três formas de organizar a mesma conversa. Não é uma escada de evolução. É uma escolha de interface.

O preço de cada abstração

Nenhuma escolha é de graça. A diferença que mais pesa num app fullstack TypeScript:

  • REST e GraphQL são agnósticos de linguagem. O servidor pode ser Go, o cliente pode ser Swift, e o contrato (OpenAPI, schema GraphQL) vale pra qualquer um. É a escolha certa quando consumidores variados batem na sua API.
  • tRPC é acoplado ao TypeScript. O cliente importa o tipo do servidor direto, sem geração de código, sem schema intermediário. Você troca universalidade por type-safety automática de ponta a ponta. Num monorepo onde front e back falam TypeScript, isso é uma alavanca de velocidade enorme.

Repare o que isso significa: a type-safety do tRPC é de tempo de compilação. Depois do build, o tipo é apagado e o que viaja é JSON puro, igual ao de REST. Por isso o tRPC ainda valida o input em runtime com Zod. O tipo te protege enquanto você escreve; o Zod protege quando o request chega.

Onde tudo isso vive: a camada 7 da OSI

Aqui a peça se encaixa. REST, GraphQL e tRPC vivem todos na camada 7 (Aplicação) do modelo OSI. Tudo abaixo é idêntico nos três. É por isso que a abstração do RPC "funciona": ela só troca a interface do topo, e a pilha inteira embaixo continua a mesma.

#CamadaExemplo no stack serverlessVocê mexe?
7AplicaçãoHTTP + REST/GraphQL/tRPC, webhookssempre
6ApresentaçãoTLS, JSON/superjson, gzip/brotliindireto
5Sessãohandshake TLS, keep-alive, reuso de conexãoraramente
4TransporteTCP :443 (browser→app), TCP :5432 (app→banco), poolingpool do banco
3RedeIP do usuário (x-forwarded-for), Anycast da EdgeremoteIp
2EnlaceEthernet/MAC dentro do datacenternão
1Físicafibra, rádio, hardwarenão

Trocar REST por GraphQL por tRPC mexe só na camada 7. O TCP, o TLS, o pooling de conexão com o banco, nada disso muda. Quando você entende que a discussão inteira acontece num único andar do prédio, ela para de parecer uma guerra de tecnologias e vira o que é: escolha de vocabulário.

E o serverless não muda as camadas, muda quem as opera. Numa VPS você cuidava de Nginx (7), TLS (6), portas e firewall (4/3). No serverless, as camadas 1 a 6 são da plataforma; você fica na 7 (lógica) e num pedaço da 4 (o pool do banco). É isso que chamam de "zero-ops".

No app fullstack TypeScript: tRPC vs Server Action

No Next.js a coisa fica interessante porque o framework tem seu próprio RPC nativo: as Server Actions. Uma Server Action também esconde o HTTP atrás do que parece uma chamada de função. Então a pergunta natural é: se a Server Action já é RPC, pra que tRPC?

A resposta é o que vem junto com cada um. Olha o mesmo create dos dois jeitos.

Com tRPC, auth, validação e escopo de organização são declarativos:

create: protectedProcedure                       // AUTH automática
  .input(z.object({ orgSlug, eventId, code }))   // VALIDAÇÃO declarativa
  .mutation(async ({ ctx, input }) => {
    const { org } = await requireOrgMember(ctx, input.orgSlug, ADMIN_ROLES);
    // a partir daqui, só lógica de negócio
    return ctx.db.coupon.create({ data: { /* ... */ } });
  }),

Com Server Action, tudo isso é na mão, toda vez:

"use server";
export async function createCoupon(raw: unknown) {
  const parsed = schema.safeParse(raw);              // 1. validação na mão
  if (!parsed.success) return { ok: false };
  const session = await auth();                      // 2. auth na mão
  if (!session?.user) return { ok: false };
  const member = await db.member.findUnique({ /*...*/ }); // 3. org-scoping na mão
  if (!member || !hasRole(member.role, ADMIN_ROLES)) return { ok: false };
  // só agora a lógica de negócio
}

O perigo é concreto: no Server Action, esquecer o bloco de auth ou de org-scoping compila e sobe em produção. Vira um furo onde uma organização acessa dados de outra. No tRPC, protectedProcedure e requireOrgMember são explícitos e consistentes em todas as rotas.

Não é "tRPC é melhor". É saber o que cada um cobra:

  • tRPC brilha quanto mais regras de permissão e multi-tenant você tem. Ele também te dá a camada de leitura com cache no cliente (TanStack Query).
  • Server Action brilha em formulário simples, sem org nem role (newsletter, contato), onde a cerimônia do tRPC é overhead e o progressive enhancement do form nativo é uma vantagem.

A regra que uso: tRPC como espinha dorsal da API de dados (queries e mutations protegidas), Server Actions pros formulários simples ocasionais. Misturar é saudável.

A regra prática

Não escolha REST, GraphQL ou RPC pela moda. Escolha a interface pelo formato do problema, e lembre que o transporte é o mesmo nos três.

Se a sua API é consumida por clientes variados, em linguagens diferentes, REST ou GraphQL pagam pela universalidade do contrato. Se é um app fullstack TypeScript num monorepo, tRPC te dá type-safety de ponta a ponta sem codegen. E independente da escolha, lembre que tudo acontece na camada 7: o RPC não te deixa mais perto do servidor, ele só esconde o fetch com mais elegância.

A discussão "qual é melhor" quase sempre é a pessoa vendendo a interface que ela domina. A pergunta madura é outra: quem consome essa API, em que linguagem, e quanto a type-safety automática vale pro seu time?


TL;DR: REST, GraphQL e RPC/tRPC são a mesma chamada remota sobre HTTP, todos na camada 7 da OSI; o que muda é a interface mental (recurso, grafo, função) e o preço (REST/GraphQL são agnósticos de linguagem; tRPC troca isso por type-safety automática acoplada ao TypeScript). Num app fullstack TS, tRPC e Server Action são ambos RPC: tRPC vence quando há permissões e multi-tenant, Server Action vence em formulário simples. Trocar de interface mexe só no topo da pilha; o TCP, o TLS e o pooling continuam iguais.

Escrito por IA, revisado por Thiago Marinho

9 de junho de 2026 · Brazil