tRPC: APIs type-safe do jeito que TypeScript prometia
1. O problema que tRPC resolve: a desconexão entre frontend e backend
1.1. A promessa quebrada do TypeScript: tipos que se perdem na rede
TypeScript prometeu tipagem forte de ponta a ponta. Mas, na prática, os tipos morrem na fronteira entre frontend e backend. Você define interfaces no servidor, copia manualmente para o cliente e reza para que não haja divergência. Um campo renomeado no backend vira um bug silencioso no frontend. O TypeScript não consegue atravessar a rede — até agora.
1.2. REST e GraphQL: o custo de manter contratos manuais entre cliente e servidor
Com REST, você mantém URLs, métodos HTTP e schemas de resposta manualmente. Um GET /users/:id pode retornar { name, email } hoje e { nome, email } amanhã — o frontend só descobre em runtime. GraphQL resolve parte do problema com schemas centralizados, mas exige queries complexas, resolvers e ferramentas como Apollo. Ambos demandam code generation ou documentação externa para manter tipos sincronizados.
1.3. Como tRPC elimina a necessidade de schemas duplicados e code generation
tRPC (TypeScript Remote Procedure Call) permite que você defina procedimentos no servidor e os consuma no cliente como se fossem funções locais. O TypeScript infere automaticamente os tipos de entrada e saída. Sem schemas duplicados. Sem geradores de código. Sem arquivos .d.ts extras. A promessa original do TypeScript — tipos que fluem do servidor ao cliente — finalmente se concretiza.
2. Conceitos fundamentais do tRPC
2.1. RPC moderno: o que muda em relação a chamadas de procedimento remoto tradicionais
RPC clássico (XML-RPC, JSON-RPC, gRPC) exige definições de interface em linguagens específicas (IDL) e compilação. tRPC usa TypeScript puro como linguagem de definição. Não há stub generation. A assinatura da função no servidor é a assinatura no cliente. Isso reduz drasticamente o atrito entre frontend e backend.
2.2. O papel do @trpc/server e @trpc/client na arquitetura
@trpc/server: define roteadores (routers), procedimentos (queries, mutations, subscriptions) e middlewares. Tudo tipado.@trpc/client: consome o servidor com um proxy que reflete exatamente os tipos exportados. O cliente "enxerga" a API como um objeto JavaScript.
2.3. Inferência de tipos automática: como o TypeScript viaja do servidor ao cliente
O servidor exporta um tipo AppRouter. O cliente, ao criar uma instância com createTRPCReact<AppRouter>(), herda todos os tipos. A função trpc.user.getById.useQuery({ id: 1 }) sabe que id deve ser número e que o retorno tem os campos exatos do servidor. Mudanças no servidor refletem instantaneamente no cliente — sem erros de compilação ocultos.
3. Montando o servidor tRPC: roteadores e procedimentos
3.1. Criando um router com queries e mutations type-safe
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
export const appRouter = t.router({
user: t.router({
getById: t.procedure
.input(z.object({ id: z.number() }))
.query(async ({ input }) => {
return { id: input.id, name: 'Alice', email: 'alice@example.com' };
}),
create: t.procedure
.input(z.object({ name: z.string(), email: z.string().email() }))
.mutation(async ({ input }) => {
// lógica de criação
return { success: true, user: input };
}),
}),
});
export type AppRouter = typeof appRouter;
3.2. Uso de middlewares: autenticação, logging e validação com Zod
const isAuthed = t.middleware(async ({ ctx, next }) => {
if (!ctx.user) throw new Error('Não autenticado');
return next({ ctx: { user: ctx.user } });
});
export const authedProcedure = t.procedure.use(isAuthed);
export const adminRouter = t.router({
deleteUser: authedProcedure
.input(z.object({ userId: z.string() }))
.mutation(async ({ ctx, input }) => {
// ctx.user está tipado como usuário autenticado
return { deleted: true };
}),
});
3.3. Contexto compartilhado: passando request, headers e estado entre procedimentos
import { inferAsyncReturnType } from '@trpc/server';
import { CreateExpressContextOptions } from '@trpc/server/adapters/express';
export const createContext = ({ req, res }: CreateExpressContextOptions) => {
return {
req,
res,
user: req.headers.authorization ? { id: '123', name: 'Admin' } : null,
};
};
type Context = inferAsyncReturnType<typeof createContext>;
const t = initTRPC.context<Context>().create();
4. Consumindo a API do lado do cliente: a mágica da tipagem
4.1. Configurando o cliente tRPC para consumir o tipo do servidor
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server/router';
export const trpc = createTRPCReact<AppRouter>();
4.2. Chamadas com useQuery e useMutation (React) — sem tipagem manual
import { trpc } from '../utils/trpc';
function UserProfile({ userId }: { userId: number }) {
const { data, isLoading } = trpc.user.getById.useQuery({ id: userId });
// data é automaticamente tipado como { id: number; name: string; email: string }
const createUser = trpc.user.create.useMutation({
onSuccess: (data) => console.log('Usuário criado:', data.user),
});
return <div>{isLoading ? 'Carregando...' : data?.name}</div>;
}
4.3. Benefícios no desenvolvimento: autocompletion e refatoração sem erros
Ao digitar trpc., o autocomplete já mostra todos os roteadores disponíveis. Se você renomear um campo no servidor, o TypeScript aponta exatamente onde o cliente precisa ser ajustado. Zero surpresas em runtime.
5. tRPC vs REST vs GraphQL: comparando abordagens
5.1. Quando tRPC brilha: monolitos full-stack, aplicações internas e startups
tRPC é ideal quando frontend e backend são escritos em TypeScript e mantidos pela mesma equipe. Aplicações internas, dashboards, MVPs e monolitos full-stack se beneficiam da produtividade sem precedentes. Não há necessidade de versionar API para consumo externo.
5.2. Limitações: caching HTTP, versionamento e APIs públicas
tRPC usa HTTP POST para queries (não GET), o que quebra o caching automático de navegadores e CDNs. Versionamento de API é mais difícil — você precisa gerenciar múltiplos routers. Para APIs públicas consumidas por clientes externos (Python, Go, mobile), REST ou GraphQL ainda são mais adequados.
5.3. Cenários onde REST ou GraphQL ainda são superiores
- APIs públicas com múltiplos clientes em linguagens diferentes.
- Aplicações que dependem fortemente de caching HTTP (GET requests).
- Equipes separadas que precisam de contratos formais (OpenAPI, GraphQL SDL).
6. Integração com ecossistemas modernos
6.1. tRPC + Next.js App Router: Server Components e Actions
// app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '@/server/router';
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => ({}),
});
export { handler as GET, handler as POST };
No cliente, use trpc.react() diretamente em Server Components ou Client Components.
6.2. tRPC com React Native e aplicações mobile
import { createTRPCReact } from '@trpc/react-query';
import { httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/router';
export const trpc = createTRPCReact<AppRouter>();
export const trpcClient = trpc.createClient({
links: [httpBatchLink({ url: 'https://api.exemplo.com/trpc' })],
});
A mesma tipagem e os mesmos hooks funcionam em React Native sem alterações.
6.3. Substituindo Express por tRPC em backends Node.js existentes
Você pode manter Express para rotas não-API (arquivos estáticos, webhooks) e usar tRPC para toda a lógica de dados. A migração pode ser incremental: um router por vez.
7. Boas práticas e padrões avançados
7.1. Organização de routers: modularização e arquivos separados
// server/routers/user.ts
export const userRouter = t.router({ ... });
// server/routers/post.ts
export const postRouter = t.router({ ... });
// server/router.ts
import { userRouter } from './routers/user';
import { postRouter } from './routers/post';
export const appRouter = t.router({
user: userRouter,
post: postRouter,
});
7.2. Tratamento de erros: códigos HTTP, mensagens customizadas e Zod errors
import { TRPCError } from '@trpc/server';
t.procedure.input(z.object({ id: z.number() })).query(async ({ input }) => {
const user = await findUser(input.id);
if (!user) throw new TRPCError({ code: 'NOT_FOUND', message: 'Usuário não encontrado' });
return user;
});
Erros de validação do Zod são automaticamente convertidos em respostas HTTP 400 com detalhes dos campos.
7.3. Testando APIs tRPC: unit tests e integração sem mockar tipos
import { createCallerFactory } from '@trpc/server';
import { appRouter } from './router';
const createCaller = createCallerFactory(appRouter);
const caller = createCaller({ user: { id: 'admin' } });
test('retorna usuário por ID', async () => {
const result = await caller.user.getById({ id: 1 });
expect(result.name).toBe('Alice');
});
Sem mockar HTTP, sem stubs. Apenas chamadas de função tipadas.
8. O futuro do tRPC em 2025 e além
8.1. Adoção crescente em empresas e frameworks (TanStack Start, SolidStart)
tRPC já é a escolha padrão em frameworks como TanStack Start e SolidStart. Grandes empresas (Vercel, Cal.com, T3) usam tRPC em produção. A comunidade cresce rapidamente.
8.2. A evolução do tRPC com WebSockets e streaming
Subscriptions (WebSockets) já são suportadas. O futuro inclui streaming de dados com Server-Sent Events e integração com real-time databases como Turso e Supabase.
8.3. tRPC como padrão de facto para TypeScript full-stack
Assim como Next.js popularizou React no servidor, tRPC está pavimentando o caminho para que TypeScript full-stack seja a norma. A promessa original do TypeScript — tipos que viajam do servidor ao cliente sem atrito — finalmente se cumpre.
Referências
- Documentação oficial do tRPC — Guia completo de instalação, configuração e exemplos práticos para servidor e cliente.
- tRPC + Next.js: guia passo a passo — Tutorial oficial para integrar tRPC com Next.js App Router e Pages Router.
- tRPC vs GraphQL: comparação técnica — Artigo detalhado comparando produtividade, tipagem e performance entre as duas abordagens.
- Criando uma API full-stack com tRPC e Prisma — Tutorial da Prisma mostrando como combinar tRPC com banco de dados e validação Zod.
- tRPC em produção: lições do Cal.com — Relato de engenharia sobre como a Cal.com usa tRPC em escala com milhares de usuários simultâneos.