2 pontos por GN⁺ 2023-08-13 | 1 comentários | Compartilhar no WhatsApp
  • 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 configura router e publicProcedure
    • A procedure greeting recebe uma string name como entrada e retorna uma string no formato Hello ${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
  • Criação do servidor HTTP

    • createHTTPServer executa o servidor tRPC ao receber appRouter
    • No exemplo, a API passa a escutar na porta 3000 com listen(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 httpBatchLink para se conectar a http://localhost:3000
    • É possível usar a procedure greeting do servidor com uma chamada como trpc.greeting.query({ name: 'John' })
    • Ao passar o tipo AppRouter ao criar o cliente, você obtém autocompletar do TypeScript e IntelliSense alinhados com a API do backend, sem geração de código

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

 
GN⁺ 2023-08-13
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

    • Esta é justamente uma vantagem negligenciada dos schemas. Schemas como os do GraphQL fazem você pensar em tipos de dados e contratos, e facilitam que pessoas responsáveis por outras partes do código trabalhem de forma alinhada
      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.
    • Na empresa, adotamos tRPC e, com um pouco de design antecipado, estamos conseguindo separar bem, dentro do código, as coisas que poderiam ficar “fortemente acopladas”
      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
    • O problema parece estar menos no tRPC e mais em engenheiros que usam tipos apenas para colocar tipos. O mesmo problema surgiria com qualquer ferramenta
      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
    • Seria bom explicar melhor a parte em que o acoplamento é um pesadelo
      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
    • Em equipes pequenas, minha experiência tem sido exatamente o oposto, e acho que também teria funcionado bem em equipes grandes nas quais trabalhei antes
      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 uma Promise; no cliente, ele é exposto como uma função assíncrona com os mesmos tipos de argumentos e retorno
    Usamos 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

    • O projeto atual combina frontend em TypeScript e backend em Python, e usamos o schema OpenAPI como fonte da verdade, com openapi-typescript-codegen [0] no lado do cliente
      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
    • Essa abordagem está certa. O tRPC adiciona complexidade desnecessária além de simplesmente inferir tipos
      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
    • Tenho curiosidade se há algum macete para fazer validação dos dados da requisição nesse processo
      Ao usar tRPC, JSON Schema com geração de tipos, Zod e coisas do tipo, esse é justamente um valor importante
    • Olhando o código de exemplo, fico me perguntando se isso não está mais para um wrapper tipado em cima de chamadas XHR
      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
    • Essa abordagem realmente funciona muito bem
      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

    • Garph é algo como tRPC para GraphQL: https://garph.dev
      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
    • Seria bom se você explicasse melhor a parte de que “fez a comunidade GraphQL reconhecer os limites e as concessões de uma linguagem de consulta”
      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
    • O que sempre se destaca é que verbos e caminhos são detalhes bem triviais
      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)

    • Não gosto muito do Zod. Os tipos e parâmetros genéricos são difíceis e verbosos; não dá para passar tipos de dados para genéricos de objetos, é preciso passar o próprio tipo de schema do Zod como propriedade, o que fica bagunçado e pouco intuitivo
      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
    • Usei tRPC e Zod recentemente em um projeto pessoal, e concordo que foi uma experiência realmente excelente
      Escrever testes unitários também ficou muito mais fácil
    • Fico curioso se existe alguma ferramenta nessa área para MongoDB
      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
    • Zod é absurdamente bom
      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
    • Também recomendo effect/core e effect/schema
      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

    • É quase um “você precisa fazer isso por conta própria”
      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
    • Por isso é uma ferramenta para web apps
  • 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

    • Nunca ouvi falar de RSC e a busca também não ajuda muito. Seria bom ter um link
    • Não sei o que a Royal Shakespeare Company tem a ver com RPC
      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

    • Fico curioso para saber se agora funciona direito também com o Next 13 e Server Components
  • 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