Usar tRPC para APIs type-safe significa compartilhar os tipos do servidor diretamente com o cliente em projetos TypeScript, sem escrever esquemas REST ou GraphQL e sem gerar código. Você define procedimentos no backend, e o frontend chama essas funções com autocompletar e verificação de tipos em tempo de compilação. Se um campo muda no servidor, o editor aponta o erro no cliente na hora.
A abordagem brilha em monorepos e aplicações full stack em que front e back são escritos em TypeScript. Não é a melhor escolha para APIs públicas consumidas por outras linguagens. Abaixo você vê como o tRPC funciona, um exemplo mínimo com validação, comparações com REST e GraphQL e cuidados de produção.
O problema que o tRPC resolve
Em uma API REST tradicional, o contrato entre servidor e cliente vive em documentação ou em tipos duplicados. Quando alguém altera a resposta no backend, o frontend só descobre em tempo de execução, muitas vezes em produção.
O tRPC elimina essa duplicação: o cliente importa apenas o tipo do roteador do servidor. Nenhum código de servidor vai para o navegador, só a informação de tipos, que desaparece na compilação.
Conceitos principais
- Router: agrupa procedimentos, pode ser aninhado por domínio (usuários, pedidos etc.).
- Procedure: uma função exposta. Pode ser
query(leitura),mutation(escrita) ousubscription(tempo real). - Input: validação da entrada, normalmente feita com Zod, que também infere o tipo.
- Context: dados disponíveis em todas as chamadas, como usuário autenticado e conexão com o banco.
- Middleware: lógica reutilizável, como verificar se o usuário está logado.
Exemplo mínimo de tRPC para APIs type-safe
Servidor
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
export const appRouter = t.router({
saudacao: t.procedure
.input(z.object({ nome: z.string().min(1) }))
.query(({ input }) => {
return { mensagem: `Olá, ${input.nome}` };
}),
});
export type AppRouter = typeof appRouter;Cliente
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';
const trpc = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:3000/trpc' })],
});
const res = await trpc.saudacao.query({ nome: 'Ana' });
console.log(res.mensagem); // tipado como stringSe você passar { nome: 123 }, o TypeScript acusa o erro antes de rodar. Se o servidor enviar um valor inválido de alguma forma, o Zod rejeita a entrada em tempo de execução.
tRPC, REST e GraphQL comparados
| Critério | tRPC | REST | GraphQL |
|---|---|---|---|
| Segurança de tipos ponta a ponta | Nativa em TypeScript | Exige OpenAPI e geração de código | Exige geração de código |
| Consumo por outras linguagens | Limitado | Universal | Amplo |
| Curva de aprendizado | Baixa para quem usa TS | Baixa | Média |
| Cache HTTP | Possível em queries via GET | Natural | Mais trabalhoso |
| Melhor cenário | Monorepo full stack TS | APIs públicas | Muitos clientes com necessidades variadas |
Integração com frameworks e estado
React e Next.js
O adaptador para React usa TanStack Query por baixo, oferecendo cache, revalidação e estados de carregamento. Em Next.js, o tRPC pode rodar em rotas de API do próprio projeto.
Estado no cliente
Dados do servidor ficam no cache de queries. Para estado de interface, bibliotecas leves funcionam bem junto; veja o guia de estado global com Zustand.
Outros frameworks
Existem integrações da comunidade para Vue, Svelte e outros. Se você trabalha com Vue, o artigo sobre PWA com Vue complementa o assunto.
Autenticação, erros e boas práticas
- Crie o context lendo o token da requisição e buscando o usuário. Para tokens, veja como funciona o JWT.
- Use um middleware para criar
protectedProceduree reutilize em rotas privadas. - Lance
TRPCErrorcom códigos comoUNAUTHORIZEDeNOT_FOUND. - Valide toda entrada com Zod, mesmo em rotas internas.
- Divida o roteador por domínio para manter arquivos pequenos.
- Não exponha mensagens de erro internas ao cliente em produção.
Quando não usar tRPC
- APIs públicas para parceiros ou apps em outras linguagens.
- Equipes com backend e frontend em repositórios e linguagens diferentes.
- Cenários que exigem contrato formal versionado, como OpenAPI.
Nesses casos, REST com especificação OpenAPI ou GraphQL costuma servir melhor.
Estrutura de projeto recomendada
Organizar bem os arquivos desde o começo evita um roteador gigante e difícil de manter. Uma estrutura simples funciona para a maioria dos projetos:
src/
server/
trpc.ts // initTRPC, context, middlewares
routers/
usuario.ts
pedido.ts
root.ts // junta os routers em appRouter
client/
trpc.ts // cliente tipadoCada arquivo em routers/ exporta um roteador pequeno, e o root.ts monta o roteador principal:
import { router } from './trpc';
import { usuarioRouter } from './routers/usuario';
import { pedidoRouter } from './routers/pedido';
export const appRouter = router({
usuario: usuarioRouter,
pedido: pedidoRouter,
});
export type AppRouter = typeof appRouter;No cliente, as chamadas ficam agrupadas por domínio, como trpc.usuario.buscar.query(), o que deixa o código mais legível.
Testes
Procedimentos podem ser testados sem servidor HTTP usando createCaller, que chama o roteador diretamente com um context de teste. Isso torna testes unitários rápidos e sem mocks de rede.
Banco de dados
Combine o tRPC com um ORM tipado para manter a segurança de tipos até a consulta. Se aparecerem falhas de conexão, o guia sobre erro de conexão com banco de dados ajuda no diagnóstico.
Em resumo, a combinação de roteadores pequenos, validação com Zod e testes com createCaller mantém o projeto organizado mesmo quando a quantidade de procedimentos cresce bastante.
Desempenho e requisições em lote
O httpBatchLink agrupa várias chamadas feitas ao mesmo tempo em uma única requisição HTTP, o que reduz a sobrecarga de rede em páginas que buscam muitos dados de uma vez. Para respostas grandes, existe a opção de streaming, que entrega cada resultado assim que fica pronto. Em produção, combine isso com cache de queries no cliente e, quando fizer sentido, com cache HTTP em procedimentos de leitura públicos. Se a aplicação ficar atrás de um proxy reverso, confira os limites de tamanho de requisição e os timeouts configurados.
Perguntas frequentes
tRPC substitui REST?
Não em todos os casos. Ele substitui bem a camada de comunicação interna de um app full stack TypeScript, mas APIs públicas continuam mais adequadas em REST.
Preciso usar Zod?
Não é obrigatório, mas é o mais comum. O tRPC aceita outros validadores compatíveis.
O código do servidor vai para o navegador?
Não. O cliente importa apenas o tipo do roteador com import type, que some na compilação.
tRPC funciona sem monorepo?
Funciona, desde que o cliente tenha acesso ao tipo do roteador, por exemplo via pacote publicado. O monorepo apenas simplifica.
tRPC funciona com subscriptions em tempo real?
Sim. O tRPC suporta subscriptions por WebSocket e por Server-Sent Events, dependendo da versão e do adaptador usado. Consulte a documentação oficial para a configuração atual.
Dá para migrar aos poucos de REST para tRPC?
Dá. As rotas REST existentes podem conviver com o endpoint do tRPC no mesmo servidor, e você migra os procedimentos um a um.
O que levar dessa leitura
Adotar tRPC para APIs type-safe elimina contratos duplicados e erros de integração em projetos TypeScript full stack. Valide com Zod, organize roteadores por domínio e prefira REST quando a API for pública.

