2 puntos por GN⁺ 2023-08-13 | 1 comentarios | Compartir por WhatsApp
  • tRPC es una herramienta que comparte la inferencia de TypeScript en apps full-stack con TypeScript, reduciendo las discrepancias de tipos de API entre servidor y cliente
  • Si cambias la API del servidor, puedes ver de inmediato el impacto en el código del cliente mediante errores de TypeScript y autocompletado, lo que reduce la carga de refactorización
  • Funciona sin esquemas separados ni pasos de generación de código, lo que permite crear APIs type-safe sin aumentar el proceso de build ni la carga en runtime
  • Ofrece adaptadores para integrarse con varios entornos JavaScript, como React, Next.js, Express, Fastify, AWS Lambda, Solid y Svelte
  • El flujo básico consiste en definir procedimientos, crear un servidor HTTP y conectar un cliente pasándole el tipo AppRouter, con el objetivo de ofrecer una experiencia de desarrollo en la que el código del servidor de API se use como un SDK

Cómo tRPC cambia el desarrollo de APIs

  • tRPC mejora la productividad en aplicaciones full-stack al permitir que servidor y cliente usen la misma información de tipos de TypeScript
  • Su foco está en facilitar la creación de APIs type-safe de extremo a extremo y reducir el trabajo repetitivo de las capas de API tradicionales
  • Cuando un cambio del lado del servidor afecta el uso en el cliente, TypeScript informa errores, por lo que las discrepancias de tipos en la frontera cliente-servidor pueden detectarse rápidamente

Principales características que perciben los desarrolladores

  • Seguridad de tipos automática

    • Cuando un cambio del lado del servidor impacta el código del cliente, aparece como un error de TypeScript
  • Experiencia de desarrollo rápida

    • tRPC no tiene pasos de build ni compilación
    • Puede usarse sin generación de código, carga en runtime ni procesos de build separados
  • Independencia de framework

    • Puede usarse en distintos frameworks y runtimes de JavaScript
    • También puede agregarse fácilmente a proyectos existentes
  • Autocompletado

    • Ofrece una experiencia similar a tratar el código del servidor de API como un SDK
    • Al usar endpoints, se reciben sugerencias basadas en tipos
  • Huella pequeña en el cliente

    • tRPC no tiene dependencias y el tamaño del lado del cliente es pequeño
  • Adaptadores incluidos

    • Ofrece adaptadores para React, Next.js, Express, Fastify, AWS Lambda, Solid, Svelte y más

Flujo básico de uso

  • Una API tRPC comienza definiendo primero un procedimiento (procedure)
  • Los procedimientos son funciones que componen el backend; son combinables y pueden crearse como queries, mutaciones o suscripciones
  • Varios procedimientos se agrupan en un router
  • Definición de procedimientos

    • En el ejemplo, se crea una instancia de tRPC con initTRPC.create() y se configuran router y publicProcedure
    • El procedimiento greeting recibe como entrada un string name y devuelve un string con el formato Hello ${input.name}
    • Para validar la entrada se usa Zod, de modo que la entrada del cliente coincida exactamente con la forma esperada por el procedimiento
    • Al final del archivo, se exporta el tipo del router con export type AppRouter = typeof appRouter; para que pueda usarse en el código del frontend
  • Creación del servidor HTTP

    • Se pasa appRouter a createHTTPServer para ejecutar el servidor tRPC
    • En el ejemplo, se llama a listen(3000) para que la API escuche en el puerto 3000
    • tRPC ofrece adaptadores para Next.js, Express, entornos basados en Fetch API, Fastify, AWS Lambda y servidores HTTP vanilla de Node
    • Como ejemplos de entornos basados en Fetch API se incluyen Astro, Remix, SvelteKit y Cloudflare Workers
  • Conexión del cliente y ejecución de queries

    • Una vez que el servidor está en ejecución, se puede crear un cliente con createTRPCClient<AppRouter> y consultar datos
    • El cliente de ejemplo usa httpBatchLink para conectarse a http://localhost:3000
    • Se puede usar el procedimiento greeting del servidor llamándolo como trpc.greeting.query({ name: 'John' })
    • Al pasar el tipo AppRouter al crear el cliente, se obtiene autocompletado de TypeScript e IntelliSense alineados con la API del backend, sin generación de código

Plantilla de inicio e intención de creación

  • Se ofrece Use this template como plantilla para probarlo directamente
  • El creador de tRPC lo hizo para reducir la necesidad de una capa de API tradicional y dar confianza de que la app no se romperá, incluso al iterar rápido durante el desarrollo
  • tRPC es utilizado por equipos tecnológicos líderes y varias empresas Fortune 500

Reacción de los desarrolladores

  • En varias citas de desarrolladores, tRPC se valora como una herramienta que mejora la calidad del código, la velocidad de entrega, la satisfacción del desarrollador, la refactorización en la frontera cliente-servidor, la validación de entradas y la experiencia de middleware tipado
  • En monorepos TypeScript, se percibe como una opción más simple que REST plano o GraphQL, pero con tipado fuerte
  • Se presenta un caso de uso en el que, incluso al devolver payloads de la API de Stripe desde el servidor, los componentes React pueden recibir el tipo de los datos de respuesta

1 comentarios

 
GN⁺ 2023-08-13
Opiniones de Hacker News
  • Ahora mismo estamos en proceso de quitar tRPC de nuestra base de código, y fue una pesadilla por el fuerte acoplamiento
    También tenía el efecto de llevar a los desarrolladores junior a no pensar en interfaces ni en patrones de acceso a datos, y terminamos con un mapeo que iba directo desde Prisma hasta los componentes
    Es excelente para prototipado rápido, pero si después quieres separar la base de código, rápidamente quedas atrapado en un callejón sin salida

    • Esta es justamente una ventaja pasada por alto de los esquemas. Un esquema como GraphQL te obliga a pensar en los tipos de datos y en los contratos, y facilita que las personas encargadas de otras partes del código trabajen de forma coordinada
      Como también se extiende a lenguajes fuera de TypeScript, ayuda cuando migras el backend a otro lenguaje o cuando creas clientes móviles nativos como Swift o Kotlin
    • En la empresa adoptamos tRPC y, con un poco de diseño previo, estamos separando bien dentro del código las cosas que podrían quedar “fuertemente acopladas”
      tRPC es excelente, pero al final no es más que la capa de transporte entre el backend y el frontend
      Dejar que la estructura interna de tRPC se use en lo profundo de la lógica de negocio es tan malo como no tener una capa de controladores o routers que defina claramente las entradas, los esquemas y la separación
      Por eso creo que, incluso si más adelante nos alejamos de tRPC, sería relativamente sencillo; por ejemplo, tampoco parece difícil mover un subsistema completo para que funcione sobre una cola
    • El problema no es tRPC, sino más bien que hay ingenieros que usan tipos solo por poner tipos. Con cualquier herramienta pasaría lo mismo
      Hay una curva de aprendizaje para estas cosas, y normalmente empieza con tipos inútiles que no explican nada, como type FunctionIWroteTodayArgs = …
      Tras varias iteraciones, uno empieza a darse cuenta de que el objetivo no es duplicar el código, sino describir el dominio y crear tipos, interfaces y APIs reutilizables y útiles
      Por eso creo que, en vez de eliminar tRPC, lo correcto sería mejorar esa parte junto con el equipo
    • Me gustaría que explicaras más esa parte de que el acoplamiento es una pesadilla
      No veo bien por qué declarar un cliente HTTP del lado del servidor y consumirlo del lado del cliente sería peor
      Nosotros creamos servicios y usamos un patrón en el que todos los consumidores, como interfaces web o CLI, los usan; que esas cosas no se rompan ha sido una mejora mayor que cualquier enfoque que haya visto antes
    • En un equipo pequeño estamos teniendo la experiencia totalmente opuesta, y creo que también habría funcionado bien en equipos grandes anteriores
      La queja de que los desarrolladores junior no piensan en las interfaces probablemente aparecería igual con cualquier API, no solo con tRPC
  • En Notion venimos usando un estilo de API parecido a tRPC, y esa API existía unos 4 años antes que tRPC
    Con los tipos mapeados de TypeScript, algo así es fácil de crear por cuenta propia. Basta con crear un tipo de objeto donde las claves sean los nombres de las APIs y los valores sean tipos { request, response }
    Del lado del servidor, cada handler de API se define como una función que recibe APIs["addUser"]["request"] y devuelve una Promise; del lado del cliente, se expone como una función asíncrona con los mismos argumentos y tipo de retorno
    Usamos esta estrategia para APIs internas por HTTPS, APIs en tiempo real basadas en WebSocket, IPC entre Electron y Webview, e IPC de webview del sistema operativo entre iOS/Android nativo y Webview
    En las APIs nativas, el lado del servidor está en Swift o Kotlin, así que reescribimos a mano en TypeScript los tipos de request/response. Algún día cambiaremos a un formato binario con nuestro propio IDL, pero para una única API cross-language que crece lentamente, el costo en experiencia de desarrollo de algo como Protobuf todavía no nos pareció que valiera la pena

    • El proyecto actual combina un frontend en TypeScript con un backend en Python, y tomamos el esquema OpenAPI como fuente de verdad usando openapi-typescript-codegen [0] del lado del cliente
      No es perfecto, pero ofrece una interfaz de API bastante buena con tipos para request/response
      Además, creamos un wrapper de API mock de unas 10 líneas que se puede llamar como mockApi((request) => response), verifica por tipos que la función mock implemente correctamente la API y devuelve una función que se ve exactamente igual que la función de API real
      [0]: https://github.com/ferdikoomen/openapi-typescript-codegen
    • Este es el enfoque correcto. tRPC agrega complejidad innecesaria más allá de simplemente inferir tipos
      Creo que no se habla tanto de esto porque todavía no apareció una biblioteca bien mantenida y promocionada que adopte este enfoque
    • Me da curiosidad si hay algún truco para hacer la validación de datos de request en este proceso
      Esa es justamente una parte importante del valor cuando se usan tRPC, JSON Schema con generación de tipos, Zod y cosas por el estilo
    • Al ver el código de ejemplo, me pregunto si no es más bien un wrapper con tipos sobre llamadas XHR
      Incluso pide proporcionar manualmente el argumento genérico al llamar, lo cual es bastante malo cuando intentas mantener ordenado el árbol de dependencias
    • Este enfoque en realidad funciona bastante bien
      https://github.com/mikew/transmission-material-ui/blob/maste...
      https://github.com/mikew/transmission-material-ui/blob/maste...
  • Me gusta muchísimo tRPC. Me parece sorprendente cómo llevó la experiencia de desarrollador hasta el límite en un stack exclusivo de TypeScript, y logró que la comunidad de GraphQL reconociera los límites y compromisos de un lenguaje de consultas
    Al mismo tiempo, tRPC pasó muy rápido por su ciclo de moda, y no parece que esté ocurriendo una migración masiva de REST y GraphQL hacia RPC
    Dicho eso, hoy parece haber mucho interés en RPC, y en el framework BFF que creamos (https://wundergraph.com/) también adoptamos algunas ideas de tRPC y del NextJS anterior para combinar ruteo basado en archivos con RPC
    Además de tRPC, generamos automáticamente JSON Schema para cada operación, y también una especificación OpenAPI para el conjunto completo de operaciones
    A la gente le gusta este enfoque porque se puede compartir fácilmente un conjunto de endpoints RPC como una especificación OpenAPI o una colección de Postman. Casi no hay discusión sobre verbos HTTP; en la práctica solo hay consultas, cambios y suscripciones
    Me da curiosidad cómo están usando hoy APIs de estilo GraphQL, REST y RPC, y cuántas personas o equipos participan en las APIs

    • Garph es algo como tRPC para GraphQL: https://garph.dev
      Para REST APIs están ts-rest(https://ts-rest.com), zodios(https://www.zodios.org) y Hono(https://hono.dev)
      Si en el equipo usan varios lenguajes, también está Fern: https://www.buildwithfern.com
    • Me gustaría que explicaras más la parte de “hizo que la comunidad de GraphQL reconociera los límites y compromisos de un lenguaje de consultas”
      Nosotros generamos tipos de GraphQL cada vez que cambia el esquema federado, y generamos los tipos de respuesta de consultas y cambios cada vez que se guarda un archivo
    • Algo que siempre llama la atención es que los verbos y las rutas son detalles bastante menores
      También se pueden abstraer fácilmente con una biblioteca generadora de consumidores de endpoints, pero fuera de eso no sé qué se obtiene realmente en comparación con una API web común
      De todos modos, lo que hay que hacer en cada llamada al backend se tiene que hacer igual
  • Me gusta tRPC. Es, por lejos, la mejor experiencia de desarrollo full-stack que he visto hasta ahora, y especialmente al usarlo con Zod las APIs quedan realmente excelentes
    Veo a Zod y tRPC como proyectos importantes para el futuro de TypeScript, y creo que en los próximos años florecerá con fuerza en todo el ecosistema de TypeScript una experiencia de desarrollo inspirada en tRPC
    Proyectos que ya tienen ADN claramente de tRPC, pero apuntan a otros casos de uso, son UploadThing de Ping(https://github.com/pingdotgg/uploadthing) y nuestro Lusat(https://github.com/lusatai/lusat)

    • Zod no me gusta mucho. Los tipos y parámetros genéricos son complicados y verbosos; no se pueden pasar tipos de datos a genéricos de objetos, sino que hay que pasar los propios tipos de esquema de Zod como propiedades, lo cual queda desordenado y poco intuitivo
      También es molesto que el tipo de los errores de validación cambie según qué esquema se esté verificando. El manejo de errores se vuelve impredecible y siempre hay demasiados casos excepcionales
      Hay mucho margen de mejora
    • Hace poco usé tRPC y Zod en un proyecto personal, y coincido en que fue una experiencia realmente excelente
      Escribir pruebas unitarias también se volvió mucho más fácil
    • Me pregunto si existe alguna herramienta de este tipo para MongoDB
      Creo que el gran problema es el CRUD type-safe y las migraciones de datos; se menciona mucho Mongoose, pero en términos de seguridad de tipos se siente como un retroceso importante frente a Zod/TypeScript
    • Zod es realmente increíble
      Ahora tengo ganas de exagerar un poco, pero en ciertos estilos de programación diría incluso que agregar Zod a una base de código aporta un beneficio tan grande como agregar TypeScript
    • También recomiendo effect/core y effect/schema
      El patrón de partir de un esquema para crear servicios tipados encaja bien con quienes tienen inclinación por la programación funcional
  • Me pregunto cómo maneja tRPC las diferencias de versión y las migraciones
    Los campos tienen un ciclo de vida. Cuando se introducen por primera vez, ningún cliente ni servidor conoce ese campo
    Los clientes y servidores no se reinician todos a la vez, ni tienen la misma versión de los tipos
    Los datos terminan escribiéndose con una versión de los tipos y leyéndose con otra versión
    Si se puede garantizar que todos los binarios, programas en ejecución y datos se actualizan, y no hay datos persistentes, quizá no sea un problema
    Pero en cuanto hay varias organizaciones involucradas, es difícil garantizar que todas las apps y servidores estén sincronizados con la versión más reciente de la biblioteca, recompilados y redesplegados
    La verificación estática de tipos asume que no hay diferencias de versión. Todo el código dentro del binario es un mundo cerrado construido con la misma versión de la biblioteca que define los tipos

    • Es casi algo de “lo tienes que hacer tú mismo”
      Zod puede permitir campos desconocidos, luego puedes pasarlos a campos opcionales y, cuando la situación se estabilice, convertirlos en campos obligatorios
      Claro que justo el público que más quiere lo que tRPC ofrece es el menos propenso a pensar en este tipo de problemas
    • Por eso es una herramienta para aplicaciones web
  • Si se apunta a un solo lenguaje, es obvio que no se necesita esquema ni generación de código

  • Usé tRPC en dos aplicaciones web de unas 50 mil líneas y me gusta mucho. La experiencia de desarrollador es excelente
    Dicho eso, el momento de moda de tRPC ya pasó hace bastante, y siento que la moda de RSC lo alcanzó rápidamente
    Hoy todos hablan solo de si usar RSC o no, aun cuando existe una solución tan estable y buena como tRPC
    No estoy en contra de RSC, pero hay demasiada discusión. Para crear aplicaciones hoy, tRPC es un enfoque muy práctico
    Agregado: RSC significa React Server Components y trae consigo su propia filosofía de datos de lectura/escritura

    • Nunca había oído hablar de RSC y buscarlo no ayuda mucho. Estaría bien tener un enlace
    • No sé qué tiene que ver la Royal Shakespeare Company con RPC
      Estaría bien definir o enlazar las siglas que la gente podría no conocer
  • Usé tRPC y Next.js en algunos proyectos personales, y fue una buena experiencia.
    Especialmente si se usa junto con plantillas preconfiguradas como Create T3 App (https://create.t3.gg/), es difícil igualar la velocidad de iteración.

    • Me da curiosidad si ahora ya funciona correctamente con Next 13 y los componentes de servidor.
  • No lo conocía hasta que un desarrollador con el que trabajaba habló maravillas de tRPC, pero una vez que lo entendí, me pareció algo obviamente muy bueno.
    Juntos hicimos una app T3 (tRPC, Next.js, Tailwind, TypeScript, Prisma), y si quieren verla, está aquí: https://github.com/stytchauth/stytch-t3-example
    Al trabajar con TypeScript, una API con seguridad de tipos ayuda muchísimo.