Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

tRPC no Node.js: Guia Prático

Atualizado em: 1 de setembro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Usar tRPC no Node.js permite criar APIs totalmente tipadas entre servidor e cliente TypeScript sem manter um schema separado ou executar uma etapa de geração de código. O servidor define procedures, entradas e saídas, e o cliente recebe inferência automática para chamadas, erros e respostas.

O ganho de produtividade é especialmente forte em aplicações full-stack que compartilham o mesmo repositório ou pacote de tipos. Porém, type safety em tempo de compilação não substitui validação runtime, autenticação, autorização, limites, versionamento nem observabilidade. Dados enviados pela rede continuam não confiáveis.

Neste guia, você aprenderá a configurar tRPC 11, criar routers e procedures, validar entradas, construir context, proteger rotas, integrar com HTTP, aplicar batching, tratar erros, usar subscriptions e testar a API.

O que é tRPC?

tRPC é uma biblioteca para APIs end-to-end typesafe. A documentação oficial do tRPC explica que a versão 11 oferece inferência de tipos sem geração de código e possui adapters para Node.js e frameworks populares. A documentação do TypeScript apresenta os recursos de tipos usados pela biblioteca.

Quando usar?

  • frontend e backend em TypeScript;
  • monorepo ou packages compartilhados;
  • painéis internos;
  • aplicações web full-stack;
  • BFFs controlados pela mesma equipe;
  • migração gradual de endpoints.

Quando evitar?

  • API pública para linguagens variadas;
  • contrato que precisa de OpenAPI independente;
  • clientes externos sem TypeScript;
  • equipes que precisam desacoplar releases completamente;
  • protocolos com governança formal entre empresas.

Para contratos públicos, consulte OpenAPI com Node.js.

Instalando

npm install @trpc/server @trpc/client zod

Zod é uma opção comum para validar entradas. A API exata deve acompanhar a versão instalada.

Inicializando tRPC

import { initTRPC } from '@trpc/server';

export type Context = {
  requestId: string;
  auth: AuthContext | null;
  services: Services;
};

const t = initTRPC.context<Context>().create();

export const router = t.router;
export const publicProcedure = t.procedure;

O context é criado por requisição e pode carregar identidade, serviços, repositories e request ID.

Router básico

import { z } from 'zod';

export const appRouter = router({
  health: publicProcedure.query(() => ({
    status: 'ok'
  })),

  userById: publicProcedure
    .input(z.object({
      id: z.string().uuid()
    }))
    .query(({ input, ctx }) => {
      return ctx.services.users.findById(input.id);
    })
});

export type AppRouter = typeof appRouter;

O tipo AppRouter é exportado para o cliente sem enviar código do servidor ao navegador.

Cliente tipado

import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server/router';

const client = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'https://app.example.com/trpc'
    })
  ]
});

const user = await client.userById.query({
  id: userId
});

O editor conhece entrada e resposta. Mudanças incompatíveis aparecem durante build dos clientes que compartilham o tipo.

Type safety não valida a rede

Um cliente malicioso pode enviar qualquer JSON. Por isso, toda procedure que recebe input precisa de schema runtime:

.input(z.object({
  name: z.string().min(1).max(100),
  email: z.string().email()
}).strict())

O artigo sobre validação com Zod será incluído nesta mesma rodada.

Queries e mutations

  • query: leitura sem efeito relevante;
  • mutation: criação, alteração ou exclusão;
  • subscription: atualizações em tempo real.
createUser: protectedProcedure
  .input(CreateUserSchema)
  .mutation(({ input, ctx }) => {
    return ctx.services.users.create({
      tenantId: ctx.auth.tenantId,
      actorId: ctx.auth.userId,
      input
    });
  })

Context por requisição

export async function createContext({ req }) {
  const requestId = resolveRequestId(req);
  const auth = await authenticate(req);

  return {
    requestId,
    auth,
    services
  };
}

Nunca guarde usuário ou tenant em variável global.

Middleware de autenticação

import { TRPCError } from '@trpc/server';

const requireAuth = t.middleware(({ ctx, next }) => {
  if (!ctx.auth) {
    throw new TRPCError({
      code: 'UNAUTHORIZED',
      message: 'Autenticação necessária'
    });
  }

  return next({
    ctx: {
      ...ctx,
      auth: ctx.auth
    }
  });
});

export const protectedProcedure = t.procedure.use(requireAuth);

Autorização

function requirePermission(permission) {
  return t.middleware(({ ctx, next }) => {
    if (!ctx.auth?.permissions.includes(permission)) {
      throw new TRPCError({ code: 'FORBIDDEN' });
    }

    return next();
  });
}

Consulte RBAC no Node.js e ABAC no Node.js.

Autorização por recurso

Uma permission geral não basta. A consulta deve incluir tenant e ownership:

const project = await ctx.services.projects.findOne({
  tenantId: ctx.auth.tenantId,
  projectId: input.id
});

Não busque apenas por ID e valide depois quando o banco pode aplicar o escopo desde o início.

Multi-tenancy

O tenant vem da identidade autenticada, nunca de um campo livre no input. Consulte Multi-Tenancy no Node.js.

Adapters HTTP

tRPC pode rodar com servidor HTTP nativo, Express, Fastify, Next.js e outros adapters. Escolha a integração que oferece controle de cookies, CORS e shutdown para sua aplicação.

Servidor HTTP standalone

import { createHTTPServer } from '@trpc/server/adapters/standalone';

const server = createHTTPServer({
  router: appRouter,
  createContext
});

server.listen(3000);

Integração com Express

app.use('/trpc', createExpressMiddleware({
  router: appRouter,
  createContext
}));

Aplique body limits, CORS e middlewares antes da integração conforme necessário.

Request batching

O httpBatchLink pode combinar chamadas próximas em uma única requisição HTTP. Isso reduz overhead de rede.

Limites de batching

Defina quantidade e tamanho máximos. Um cliente não deve agrupar centenas de procedures caras em um único request para contornar rate limiting.

Rate limiting

Limite por procedure, usuário, tenant e custo. Consulte Rate Limiting no Node.js.

Complexidade

tRPC não possui queries arbitrariamente profundas como GraphQL, mas uma procedure ainda pode receber listas enormes, filtros caros e múltiplos itens em batch.

Paginação

.input(z.object({
  cursor: z.string().optional(),
  limit: z.number().int().min(1).max(100).default(20)
}))

Consulte Paginação em APIs Node.js.

Transformação de dados

JSON não preserva Date, Map, BigInt e outros tipos automaticamente. Use serializer suportado apenas quando necessário e mantenha clientes compatíveis.

SuperJSON

É uma opção comum para serializar tipos adicionais. Avalie tamanho, segurança e compatibilidade de versão.

Não exponha modelos internos

Mesmo com tipos compartilhados, transforme entidades para DTOs. Não retorne hash de senha, tokens, flags internas ou colunas privadas.

Erros

throw new TRPCError({
  code: 'NOT_FOUND',
  message: 'Recurso não encontrado'
});

Use códigos estáveis e mensagens seguras. Não envie stack, SQL ou hostname.

Error formatter

É possível adicionar request ID e informações controladas:

errorFormatter({ shape, ctx }) {
  return {
    ...shape,
    data: {
      ...shape.data,
      requestId: ctx?.requestId
    }
  };
}

Erros de validação

Mapeie issues do schema sem expor detalhes internos. Campos podem receber códigos específicos para a interface.

CORS

Use allowlist de origens. Consulte CORS em APIs Node.js.

Cookies e CSRF

Se autenticação usa cookie, proteja mutations contra CSRF. SameSite ajuda, mas não substitui uma estratégia quando há uso cross-site.

JWT

Quando usar bearer token, valide assinatura, issuer, audience e expiração. Consulte JWT Seguro no Node.js.

Subscriptions

tRPC suporta atualizações em tempo real conforme adapter. A autenticação precisa ser validada na conexão e durante o ciclo quando permissões podem mudar.

WebSocket e SSE

Escolha transporte conforme bidirecionalidade e infraestrutura. Consulte WebSocket no Node.js.

Cancelamento

Propague AbortSignal para bancos e fetches quando possível. Se o cliente cancela, evite continuar trabalho caro sem necessidade.

Timeouts

Cada procedure deve respeitar um orçamento. Downstreams precisam de prazo menor que o timeout total.

Transações

Mutations que alteram várias tabelas devem delegar a uma camada de serviço transacional. Consulte Transações PostgreSQL no Node.js.

Idempotência

Mutations de pagamento e criação podem receber uma chave de idempotência. Veja Idempotência em APIs Node.js.

Cache

O cliente pode integrar com TanStack Query, mas o servidor ainda precisa definir consistência, invalidação e autorização.

Invalidation

Depois de mutation, invalide as queries relacionadas no cliente. No servidor, limpe caches compartilhados e loaders quando usados.

Monorepo

Exporte apenas o tipo do router para o frontend. Não importe módulos que executam conexão de banco ou carregam segredos.

Package de contrato

Em repositórios separados, um pacote pode publicar tipos. Isso exige versionamento e compatibilidade entre cliente e servidor.

Mudanças breaking

Renomear procedure, remover campo ou alterar input quebra clientes no build, mas clientes antigos já implantados podem continuar chamando a versão anterior.

Versionamento

Durante migração, mantenha procedure antiga e nova:

userById
userByIdV2

Marque a antiga como depreciada no código e monitore uso.

API pública

Para terceiros, considere uma camada REST/OpenAPI ou GraphQL estável. O acoplamento TypeScript do tRPC é uma vantagem interna, não um contrato universal.

Logging

Registre procedure path, tipo, duração, status, request ID e tenant. Não registre inputs completos.

Consulte Logs com Pino no Node.js.

Métricas

  • chamadas por procedure;
  • latência p95 e p99;
  • erros por código;
  • tamanho de batch;
  • rejeições de validação;
  • timeouts;
  • downstream latency.

Tracing

Crie span com procedure path e propague contexto para banco e APIs. Evite usar input como atributo.

Health check

Mantenha uma rota HTTP simples fora do tRPC para probes, ou uma procedure pública leve quando a infraestrutura permitir.

Graceful shutdown

Pare novas conexões, encerre subscriptions e aguarde requests em andamento. Consulte Graceful Shutdown no Node.js.

Testes de caller

const caller = appRouter.createCaller(testContext);

const user = await caller.userById({
  id: userId
});

Isso testa o router sem iniciar HTTP.

Teste de autenticação

Crie context sem usuário e confirme UNAUTHORIZED.

Teste de autorização

Use identidade sem permission e outro tenant. Ambos devem falhar sem acessar o repository fora do escopo.

Teste de validação

Envie UUID inválido, campos desconhecidos, strings grandes e listas acima do limite.

Teste HTTP

Além do caller, teste adapter, headers, cookies, CORS, batching e formato de erro.

Teste de compatibilidade

Compile um cliente de fixture contra o tipo exportado para detectar mudanças breaking.

Teste de carga

Meça batching, latência e downstreams. Um request HTTP com dez procedures ainda pode executar dez operações caras.

Erros comuns

  • Confiar apenas no TypeScript: input da rede não é validado.
  • Context global: identidade vaza entre requests.
  • Router com regra de negócio: manutenção fica difícil.
  • Batch sem limite: cliente contorna proteção.
  • Expor modelo interno: campos privados chegam ao frontend.
  • API externa acoplada: clientes de outras linguagens sofrem.
  • Inputs em logs: dados sensíveis aparecem.

Boas práticas

  • Valide todo input em runtime.
  • Crie context por request.
  • Use procedures protegidas.
  • Autorize por recurso.
  • Limite batches e listas.
  • Separe serviços e routers.
  • Não exponha entidades.
  • Versione mudanças.
  • Monitore por procedure.
  • Teste caller e HTTP.

Conclusão

Usar tRPC no Node.js oferece uma experiência full-stack com tipos sincronizados e sem geração de código. Procedures e clientes permanecem alinhados durante o desenvolvimento.

A segurança continua dependendo de validação runtime, autenticação, autorização e limites. Com context por request, schemas estritos e observabilidade, tRPC reduz a duplicação de contratos sem transformar tipos de compilação em uma falsa barreira contra dados maliciosos.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita