- tRPC é uma ferramenta que compartilha a inferência do TypeScript em apps full-stack TypeScript, reduzindo incompatibilidades de tipos de API entre servidor e cliente
- Ao mudar a API no servidor, é possível ver imediatamente o impacto no código do cliente por meio de erros de TypeScript e autocompletar, reduzindo o custo de refatoração
- Funciona sem schema separado nem etapa de geração de código, permitindo montar APIs type-safe sem aumentar o processo de build nem a carga em runtime
- Oferece adaptadores para vários ambientes JavaScript, como React, Next.js, Express, Fastify, AWS Lambda, Solid e Svelte
- O fluxo básico é definir procedures, criar um servidor HTTP e conectar o cliente passando o tipo
AppRouter, com a proposta de usar o código da API como se fosse um SDK
Como o tRPC muda a forma de desenvolver APIs
- tRPC aumenta a produtividade em aplicações full-stack ao fazer servidor e cliente usarem a mesma informação de tipos do TypeScript
- A ideia central é facilitar a criação de APIs type-safe de ponta a ponta e reduzir o trabalho repetitivo envolvido em camadas de API tradicionais
- Quando mudanças no servidor afetam o uso no cliente, o TypeScript aponta os erros, permitindo identificar rapidamente incompatibilidades de tipo na fronteira entre cliente e servidor
Principais características percebidas pelos desenvolvedores
-
Segurança de tipos automática
- Mudanças no servidor que impactam o código do cliente aparecem como erros de TypeScript
-
Experiência de desenvolvimento rápida
- tRPC não tem etapa de build nem de compilação
- Pode ser usado sem geração de código, sem custo extra em runtime e sem processos de build separados
-
Independência de framework
- Pode ser usado em diferentes frameworks e runtimes JavaScript
- Também é fácil de adicionar a projetos existentes
-
Autocompletar
- Proporciona a experiência de tratar o código do servidor da API como um SDK
- Ao usar endpoints, é possível receber sugestões baseadas em tipos
-
Pequeno footprint no cliente
- tRPC não tem dependências e tem tamanho reduzido no lado do cliente
-
Adaptadores incluídos
- Oferece adaptadores para React, Next.js, Express, Fastify, AWS Lambda, Solid e Svelte
Fluxo básico de uso
- Uma API tRPC começa primeiro com a definição de procedures
- Procedures são funções que compõem o backend, podem ser combinadas e podem ser criadas como queries, mutations e subscriptions
- Várias procedures são agrupadas em um router
-
Definição de procedures
- No exemplo,
initTRPC.create()cria a instância do tRPC e configurarouterepublicProcedure - A procedure
greetingrecebe uma stringnamecomo entrada e retorna uma string no formatoHello ${input.name} - Para validação de entrada, usa-se Zod, garantindo que a entrada do cliente corresponda exatamente ao formato esperado pela procedure
- No fim do arquivo,
export type AppRouter = typeof appRouter;exporta o tipo do router para uso no código de frontend
- No exemplo,
-
Criação do servidor HTTP
createHTTPServerexecuta o servidor tRPC ao receberappRouter- No exemplo, a API passa a escutar na porta
3000comlisten(3000) - tRPC oferece adaptadores para Next.js, Express, ambientes baseados em Fetch API, Fastify, AWS Lambda e servidores HTTP Node vanilla
- Como exemplos de ambientes baseados em Fetch API, são citados Astro, Remix, SvelteKit e Cloudflare Workers
-
Conexão do cliente e execução de queries
- Depois que o servidor está em execução, é possível criar um cliente com
createTRPCClient<AppRouter>e consultar dados - O cliente do exemplo usa
httpBatchLinkpara se conectar ahttp://localhost:3000 - É possível usar a procedure
greetingdo servidor com uma chamada comotrpc.greeting.query({ name: 'John' }) - Ao passar o tipo
AppRouterao criar o cliente, você obtém autocompletar do TypeScript e IntelliSense alinhados com a API do backend, sem geração de código
- Depois que o servidor está em execução, é possível criar um cliente com
Template inicial e intenção de criação
- Há um template para testar diretamente em Use this template
- O criador do tRPC desenvolveu a ferramenta para reduzir a necessidade de camadas de API tradicionais e permitir iteração rápida com confiança de que o app não vai quebrar
- tRPC já é usado por equipes técnicas de ponta e por várias empresas da Fortune 500
Reação dos desenvolvedores
- Em várias citações de desenvolvedores, o tRPC é avaliado como uma ferramenta que melhora qualidade de código, velocidade de entrega, satisfação do desenvolvedor, refatoração na fronteira cliente-servidor, validação de entrada e a experiência com middleware tipado
- Em monorepos TypeScript, ele é visto como uma opção mais simples que REST puro ou GraphQL, mas ainda com tipagem forte
- Também é apresentado um caso de uso em que payloads da API da Stripe retornados pelo servidor ainda podem ter seus tipos de resposta aproveitados em componentes React
1 comentários
Opiniões no Hacker News
No código atual, estamos no processo de remover o tRPC, e foi um pesadelo por causa do acoplamento forte
Ele também tinha o efeito de levar desenvolvedores juniores a não pensar em interfaces ou padrões de acesso a dados, e acabou surgindo um mapeamento que ia direto do Prisma até os componentes
É excelente para prototipagem rápida, mas, quando se tenta separar a base de código mais tarde, você rapidamente fica preso em um beco sem saída
Como também se estendem para linguagens fora do TypeScript, ajudam quando se migra o backend para outra linguagem ou se criam clientes mobile nativos em Swift, Kotlin etc.
tRPC é excelente, mas no fim é apenas a camada de transporte entre backend e frontend
Permitir que a estrutura interna do tRPC seja usada profundamente na lógica de negócio é tão ruim quanto não ter uma camada de controllers ou routers que defina claramente entradas, schemas e separações
Por isso, mesmo que venhamos a sair do tRPC no futuro, acho que seria relativamente simples; por exemplo, mover um subsistema inteiro para rodar sobre uma fila também não parece difícil
Há uma curva de aprendizado nisso, e normalmente começa com tipos inúteis e que não explicam nada, como
type FunctionIWroteTodayArgs = …Depois de algumas iterações, a pessoa vai percebendo que o objetivo não é duplicar o código, mas descrever o domínio e criar tipos, interfaces e APIs reutilizáveis e úteis
Então acho que o certo é melhorar essa parte junto com a equipe, em vez de arrancar o tRPC
Não entendo bem por que declarar um cliente HTTP no lado do servidor e consumi-lo no lado do cliente seria pior
Nós criamos serviços e usamos um padrão consumido por tudo, como interfaces web ou CLIs, e o fato de essas coisas não quebrarem foi uma melhoria maior do que qualquer abordagem que eu já tinha visto
A reclamação de que desenvolvedores juniores não pensam em interfaces provavelmente surgiria do mesmo jeito com qualquer API, não apenas com tRPC
No Notion, usamos um estilo de API parecido com o tRPC, e essa API existia cerca de 4 anos antes do tRPC
Com mapped types do TypeScript, é fácil criar algo assim por conta própria. Basta criar um tipo de objeto em que as chaves são os nomes das APIs e os valores são tipos
{ request, response }No lado do servidor, cada handler de API é definido como uma função que recebe
APIs["addUser"]["request"]e retorna umaPromise; no cliente, ele é exposto como uma função assíncrona com os mesmos tipos de argumentos e retornoUsamos essa estratégia para APIs internas via HTTPS, APIs em tempo real baseadas em WebSocket, IPC entre Electron e Webview, e IPC de WebView do SO entre iOS/Android nativos e Webview
Nas APIs nativas, como o lado servidor é em Swift ou Kotlin, reescrevemos manualmente em TypeScript os tipos de request/response. Algum dia vamos trocar isso por um formato binário com nosso próprio IDL, mas, para uma API única e cross-language que cresce devagar, o custo de experiência de desenvolvimento de algo como Protobuf ainda não pareceu valer a pena
Não é perfeito, mas fornece uma interface de API bastante boa com tipos de request/response
Também criei um wrapper de API mock de 10 linhas que pode ser chamado como
mockApi((request) => response), verifica por tipos se a função mock implementa a API corretamente e retorna uma função com a mesma aparência da função da API real[0]: https://github.com/ferdikoomen/openapi-typescript-codegen
Acho que ela não é discutida com frequência porque ainda não apareceu uma biblioteca bem mantida e bem divulgada que adote essa abordagem
Ao usar tRPC, JSON Schema com geração de tipos, Zod e coisas do tipo, esse é justamente um valor importante
Chega-se até a pedir que a pessoa forneça manualmente o argumento genérico na chamada, o que é bem ruim quando se tenta manter a árvore de dependências organizada
https://github.com/mikew/transmission-material-ui/blob/maste...
https://github.com/mikew/transmission-material-ui/blob/maste...
Gosto muito do tRPC. É impressionante como ele leva a experiência do desenvolvedor ao limite em uma stack exclusiva de TypeScript, e fez a comunidade GraphQL reconhecer os limites e as concessões de uma linguagem de consulta
Ao mesmo tempo, o tRPC passou pelo ciclo de hype muito rápido, e não parece que esteja acontecendo uma migração em massa de REST e GraphQL para RPC
Ainda assim, parece haver bastante interesse em RPC hoje em dia, e no framework BFF que criamos (https://wundergraph.com/) também adotamos algumas ideias do tRPC e do antigo NextJS, combinando roteamento baseado em arquivos com RPC
Além do tRPC, geramos automaticamente JSON Schema para cada operação e também uma especificação OpenAPI para o conjunto completo de operações
As pessoas gostam dessa abordagem porque é fácil compartilhar um conjunto de endpoints RPC como uma especificação OpenAPI ou uma coleção do Postman. Também quase não há discussão sobre verbos HTTP; na prática, há apenas consultas, alterações e assinaturas
Tenho curiosidade sobre como vocês estão usando APIs nos estilos GraphQL, REST e RPC hoje em dia, e quantas pessoas ou equipes se envolvem com a API
No lado de APIs REST, há ts-rest(https://ts-rest.com), zodios(https://www.zodios.org) e Hono(https://hono.dev)
Se a equipe usa várias linguagens, também existe o Fern: https://www.buildwithfern.com
Nós geramos tipos GraphQL sempre que o schema federado muda, e geramos os tipos de resposta de consultas e alterações sempre que um arquivo é salvo
Isso também pode ser abstraído facilmente com uma biblioteca geradora de consumidores de endpoints, mas fora isso não sei o que de fato se ganha em comparação com uma API web comum
No fim, cada chamada ao backend ainda precisa fazer exatamente o que já teria de fazer
Gosto do tRPC. É disparado a melhor experiência full-stack para desenvolvedores que já vi, e a API fica realmente excelente, especialmente quando usado com Zod
Vejo Zod e tRPC como projetos importantes para o futuro do TypeScript, e acho que nos próximos anos a experiência de desenvolvimento inspirada no tRPC vai florescer bastante em todo o ecossistema TypeScript
Projetos que já têm claramente o DNA do tRPC incluem o UploadThing da Ping (https://github.com/pingdotgg/uploadthing), voltado a outros casos de uso, e o nosso Lusat (https://github.com/lusatai/lusat)
Também é incômodo que o tipo dos erros de validação varie conforme o schema que está sendo verificado. O tratamento de erros fica imprevisível e sempre há exceções demais
Há muito espaço para melhoria
Escrever testes unitários também ficou muito mais fácil
O grande problema parece ser CRUD type-safe e migração de dados; Mongoose é bastante mencionado, mas em termos de segurança de tipos parece um grande retrocesso em relação a Zod/TypeScript
Agora estou com vontade de exagerar um pouco, mas, em certos estilos de programação, eu diria até que adicionar Zod a uma base de código traz um ganho tão grande quanto adicionar TypeScript
O padrão de começar pelo schema e criar serviços tipados combina bem com pessoas mais inclinadas à programação funcional
Fico curioso sobre como o tRPC lida com diferenças de versão e migrações
Campos têm um ciclo de vida. Quando são introduzidos pela primeira vez, nenhum cliente ou servidor conhece aquele campo
Clientes e servidores não são todos reiniciados de uma vez, nem têm a mesma versão dos tipos
Os dados acabam sendo escritos com uma versão do tipo e lidos com outra versão do tipo
Se for possível garantir que todos os binários, programas em execução e dados serão atualizados, e não houver dados persistentes, talvez isso não seja um problema
Mas, no momento em que várias organizações entram na equação, fica difícil garantir que todos os apps e servidores estejam sincronizados com a versão mais recente da biblioteca, recompilados e reimplantados
A checagem estática de tipos pressupõe que não há diferenças de versão. Todo o código dentro do binário é um mundo fechado, compilado com a mesma versão da biblioteca que define os tipos
O Zod pode permitir campos desconhecidos e, depois, você pode migrar para um campo opcional e, quando a situação estabilizar, torná-lo obrigatório
Claro que justamente o público que mais quer o que o tRPC oferece é o menos propenso a pensar nesse tipo de problema
Se você está mirando apenas uma linguagem, é natural que não precise de schema nem geração de código
Usei tRPC em dois aplicativos web de cerca de 50 mil linhas e gosto muito dele. A experiência do desenvolvedor é excelente
Ainda assim, a fase de hype do tRPC já passou há algum tempo, e parece que o hype do RSC tomou seu lugar rapidamente
Hoje em dia, mesmo existindo uma solução estável e boa como o tRPC, todo mundo só fala sobre usar ou não RSC
Não sou contra RSC, mas há discussão demais. Ao criar aplicações hoje, tRPC é uma abordagem muito prática
Adendo: RSC significa React Server Components e traz junto uma filosofia própria de leitura/escrita de dados
Seria bom definir ou colocar links em siglas que as pessoas talvez não conheçam
Usei tRPC e Next.js em alguns projetos pessoais e a experiência foi boa
Especialmente quando usado com templates pré-configurados como o Create T3 App(https://create.t3.gg/), é difícil superar a velocidade de iteração
Eu não conhecia até que um desenvolvedor com quem trabalhava elogiasse muito o tRPC, mas depois que conheci, pareceu óbvio demais que era algo muito bom
Criamos juntos um app T3 (tRPC, Next.js, Tailwind, TypeScript, Prisma), e se quiser ver, está aqui: https://github.com/stytchauth/stytch-t3-example
Ao trabalhar com TypeScript, uma API type-safe ajuda muito