2 points par GN⁺ 2023-08-13 | 1 commentaires | Partager sur WhatsApp
  • tRPC est un outil qui partage l’inférence TypeScript dans les applications TypeScript full stack, afin de réduire les divergences de types d’API entre le serveur et le client
  • Lorsqu’on modifie l’API côté serveur, on peut voir immédiatement l’impact dans le code client grâce aux erreurs TypeScript et à l’autocomplétion, ce qui réduit le coût du refactoring
  • Il fonctionne sans schéma distinct ni étape de génération de code, ce qui permet de construire une API type-safe sans alourdir le processus de build ni la charge à l’exécution
  • Il fournit des adaptateurs pour plusieurs environnements JavaScript, comme React, Next.js, Express, Fastify, AWS Lambda, Solid et Svelte
  • Le flux de base consiste à définir des procédures, créer un serveur HTTP, puis connecter le client en lui passant le type AppRouter, avec l’objectif d’offrir une expérience où le code du serveur API s’utilise comme un SDK

tRPC change la manière de développer des API

  • tRPC améliore la productivité dans les applications full stack en permettant au serveur et au client d’exploiter les mêmes informations de type TypeScript
  • Son objectif principal est de faciliter la création d’API type-safe de bout en bout, tout en réduisant le travail répétitif lié aux couches d’API traditionnelles
  • Quand un changement côté serveur a un impact sur l’usage côté client, TypeScript signale les erreurs, ce qui permet de repérer rapidement les divergences de types à la frontière client-serveur

Principales caractéristiques perçues par les développeurs

  • Sécurité de type automatique

    • Les changements côté serveur qui affectent le code client apparaissent sous forme d’erreurs TypeScript
  • Expérience développeur rapide

    • tRPC n’a pas d’étape de build ni de compilation
    • Il s’utilise sans génération de code, sans surcharge à l’exécution et sans procédure de build supplémentaire
  • Indépendance vis-à-vis du framework

    • Il peut être utilisé dans l’ensemble des frameworks et runtimes JavaScript
    • Il s’ajoute facilement à des projets existants
  • Autocomplétion

    • Il offre une expérience où le code du serveur API se manipule comme un SDK
    • On bénéficie d’aides basées sur les types lors de l’utilisation des endpoints
  • Empreinte client réduite

    • tRPC n’a pas de dépendances et sa taille côté client est faible
  • Adaptateurs inclus

    • Il fournit des adaptateurs pour React, Next.js, Express, Fastify, AWS Lambda, Solid et Svelte

Flux d’utilisation de base

  • Une API tRPC commence par la définition de procédures (procedures)
  • Les procédures sont des fonctions qui composent le backend, combinables entre elles, et peuvent prendre la forme de queries, mutations et subscriptions
  • Plusieurs procédures sont regroupées dans un routeur
  • Définition des procédures

    • Dans l’exemple, on crée une instance tRPC avec initTRPC.create(), puis on configure router et publicProcedure
    • La procédure greeting prend en entrée une chaîne name et renvoie une chaîne au format Hello ${input.name}
    • La validation des entrées utilise Zod afin que les entrées client correspondent exactement à la forme attendue par la procédure
    • À la fin du fichier, export type AppRouter = typeof appRouter; exporte le type du routeur pour qu’il puisse être utilisé dans le code frontend
  • Création du serveur HTTP

    • On lance le serveur tRPC en passant appRouter à createHTTPServer
    • Dans l’exemple, l’API écoute sur le port 3000 via l’appel à listen(3000)
    • tRPC fournit des adaptateurs pour Next.js, Express, les environnements basés sur la Fetch API, Fastify, AWS Lambda et les serveurs HTTP Node natifs
    • Parmi les exemples d’environnements basés sur la Fetch API figurent Astro, Remix, SvelteKit et Cloudflare Workers
  • Connexion du client et exécution des requêtes

    • Une fois le serveur lancé, on peut créer un client avec createTRPCClient<AppRouter> et interroger les données
    • Le client de l’exemple utilise httpBatchLink pour se connecter à http://localhost:3000
    • On peut appeler la procédure greeting du serveur avec trpc.greeting.query({ name: 'John' })
    • En passant le type AppRouter lors de la création du client, on obtient l’autocomplétion TypeScript et IntelliSense alignés sur l’API backend, sans génération de code

Template de démarrage et intention du projet

  • Un template prêt à tester est proposé ici : Use this template
  • Le créateur de tRPC l’a conçu pour réduire le besoin de couches d’API traditionnelles, permettre d’itérer rapidement tout en gardant la certitude que l’application ne se casse pas
  • tRPC est utilisé par des équipes techniques de premier plan et par plusieurs entreprises du Fortune 500

Réactions des développeurs

  • Selon plusieurs témoignages de développeurs, tRPC est perçu comme un outil qui améliore la qualité du code, la vitesse de livraison, la satisfaction des développeurs, le refactoring à la frontière client-serveur, la validation des entrées et l’expérience des middlewares typés
  • Dans les monorepos TypeScript, il est considéré comme une alternative plus simple que le REST classique ou GraphQL, tout en offrant un typage plus fort
  • Un cas d’usage présenté montre que, même si le serveur renvoie une payload de l’API Stripe, les composants React peuvent tout de même recevoir le type des données de réponse

1 commentaires

 
GN⁺ 2023-08-13
Avis de Hacker News
  • Nous sommes en train de retirer tRPC de notre base de code actuelle, et ça a été un cauchemar à cause du couplage fort
    Il y avait aussi un côté qui incitait les développeurs juniors à ne pas réfléchir aux interfaces ni aux schémas d’accès aux données, et un mapping direct s’est créé de Prisma jusqu’aux composants
    C’est excellent pour prototyper rapidement, mais dès qu’on essaie ensuite de séparer la base de code, on se retrouve très vite dans une impasse

    • C’est précisément l’un des avantages sous-estimés des schémas. Des schémas comme GraphQL obligent à réfléchir aux types de données et aux contrats, et facilitent la collaboration entre personnes responsables de différentes parties du code
      Comme ils s’étendent aussi à des langages autres que TypeScript, cela aide aussi lorsqu’on migre le backend vers un autre langage ou qu’on crée des clients mobiles natifs en Swift ou Kotlin
    • Nous avons adopté tRPC dans l’entreprise, et avec un peu de conception en amont, nous séparons bien dans le code ce qui pourrait autrement devenir « fortement couplé »
      tRPC est excellent, mais au final ce n’est qu’une couche de transport entre le backend et le frontend
      Laisser les structures internes de tRPC pénétrer profondément dans la logique métier est aussi mauvais que de ne pas avoir de couche contrôleur ou routeur qui définisse clairement les entrées, les schémas et la séparation
      Donc même si nous quittions tRPC plus tard, je pense que ce serait relativement simple ; par exemple, déplacer tout un sous-système pour qu’il tourne sur une file de messages ne devrait pas être difficile
    • Le problème n’est pas vraiment tRPC, mais plutôt des ingénieurs qui utilisent les types juste pour mettre des types. Le même problème surviendrait avec n’importe quel outil
      Il y a une courbe d’apprentissage pour ce genre de choses, et on commence souvent par des types inutiles qui n’expliquent rien, comme type FunctionIWroteTodayArgs = …
      Après quelques itérations, on comprend peu à peu que l’objectif n’est pas de dupliquer le code, mais de décrire le domaine et de créer des types, interfaces et API réutilisables et utiles
      Donc, à mon avis, plutôt que d’arracher tRPC, il vaut mieux améliorer cet aspect avec l’équipe
    • J’aimerais bien que tu expliques davantage en quoi le couplage est un cauchemar
      Je ne vois pas bien pourquoi déclarer un client HTTP côté serveur et le consommer côté client serait pire
      Nous créons des services et utilisons un modèle consommé par toutes les interfaces, comme le web ou la CLI, et le fait que ces éléments ne cassent pas a été une amélioration plus importante que toutes les approches que j’avais vues auparavant
    • Dans une petite équipe, nous avons l’expérience exactement inverse, et je pense que cela aurait aussi bien fonctionné dans une ancienne grande équipe
      La plainte selon laquelle les développeurs juniors ne réfléchissent pas aux interfaces a de bonnes chances de se produire avec n’importe quelle API, pas seulement avec tRPC
  • Chez Notion, nous utilisons depuis longtemps un style d’API similaire à tRPC, et cette API existait environ 4 ans avant tRPC
    Avec les types mappés de TypeScript, on peut facilement construire cela soi-même. Il suffit de créer un type d’objet dont les clés sont les noms d’API et les valeurs des types { request, response }
    Côté serveur, chaque handler d’API est défini comme une fonction qui prend APIs["addUser"]["request"] et renvoie une Promise, et côté client on l’expose comme une fonction asynchrone ayant les mêmes arguments et le même type de retour
    Nous utilisons cette stratégie pour des API internes HTTPS, des API temps réel basées sur WebSocket, l’IPC entre Electron et une Webview, ainsi que l’IPC de webview OS entre iOS/Android natif et une Webview
    Pour les API natives, comme le serveur est en Swift ou Kotlin, nous réécrivons à la main les types de requête/réponse en TypeScript. Un jour, nous passerons sans doute à un format binaire avec notre propre IDL, mais pour une API unique et cross-language qui grossit progressivement, le coût en expérience développeur de quelque chose comme Protobuf ne nous a pas encore semblé en valoir la peine

    • Mon projet actuel combine un frontend TypeScript et un backend Python, et nous prenons le schéma OpenAPI comme source de vérité en utilisant openapi-typescript-codegen [0] côté client
      Ce n’est pas parfait, mais cela fournit une assez bonne interface d’API avec des types pour les requêtes et les réponses
      J’ai aussi créé un wrapper d’API mock d’une dizaine de lignes qu’on peut appeler comme mockApi((request) => response) ; il vérifie par types que la fonction mock implémente correctement l’API et renvoie une fonction qui ressemble exactement à la vraie fonction d’API
      [0]: https://github.com/ferdikoomen/openapi-typescript-codegen
    • C’est la bonne approche. tRPC ajoute de la complexité inutile par rapport à une simple inférence de types
      Je pense que ce n’est pas souvent discuté parce qu’il n’existe pas encore de bibliothèque bien maintenue et bien promue qui adopte cette approche
    • Je me demande s’il y a une astuce pour gérer la validation des données de requête dans ce processus
      C’est justement une partie importante de la valeur de tRPC, de JSON Schema avec génération de types, ou de Zod
    • En regardant l’exemple de code, je me demande si ce n’est pas plutôt un wrapper typé au-dessus d’appels XHR
      On va jusqu’à demander de fournir explicitement l’argument générique lors de l’appel, ce qui est assez médiocre quand on essaie de garder un arbre de dépendances propre
    • Cette approche fonctionne en fait plutôt bien
      https://github.com/mikew/transmission-material-ui/blob/maste...
      https://github.com/mikew/transmission-material-ui/blob/maste...
  • J’adore tRPC. Je trouve impressionnant la manière dont il pousse l’expérience développeur jusqu’au bout dans une stack exclusivement TypeScript, et il a amené la communauté GraphQL à prendre conscience des limites et des compromis d’un langage de requête
    En même temps, tRPC a traversé son cycle de hype très rapidement, et je n’ai pas l’impression qu’on assiste à une migration massive de REST et GraphQL vers RPC
    Cela dit, on voit beaucoup d’intérêt pour RPC ces temps-ci, et dans le framework BFF que nous avons créé (https://wundergraph.com/), nous avons aussi repris certaines idées de tRPC et de l’ancien NextJS pour combiner routage basé sur les fichiers et RPC
    En plus de tRPC, nous générons automatiquement un JSON Schema pour chaque opération, ainsi qu’une spécification OpenAPI pour l’ensemble des opérations
    Les gens apprécient cette approche, car il est facile de partager un ensemble d’endpoints RPC sous forme de spécification OpenAPI ou de collection Postman. Il y a aussi très peu de débats sur les verbes HTTP ; en pratique, il n’y a que des requêtes, des mutations et des abonnements
    Je suis curieux de savoir comment vous utilisez aujourd’hui les API de style GraphQL, REST et RPC, et combien de personnes ou d’équipes interviennent sur vos API

    • Garph est une sorte de tRPC pour GraphQL : https://garph.dev
      Côté API REST, il y a ts-rest (https://ts-rest.com), zodios (https://www.zodios.org) et Hono (https://hono.dev)
      Si votre équipe utilise plusieurs langages, il y a aussi Fern : https://www.buildwithfern.com
    • Ce serait intéressant d’expliquer davantage la partie « il a amené la communauté GraphQL à prendre conscience des limites et des compromis d’un langage de requête »
      Nous générons des types GraphQL à chaque changement du schéma fédéré, et les types de réponse des requêtes et mutations à chaque sauvegarde de fichier
    • Ce qui saute toujours aux yeux, c’est que les verbes et les chemins sont des détails assez mineurs
      On peut facilement les abstraire avec une bibliothèque de génération de consommateurs d’endpoints, mais à part ça, je ne vois pas vraiment ce qu’on gagne par rapport à une API web classique
      De toute façon, il faut quand même faire ce que chaque appel backend doit faire
  • J’aime tRPC. C’est de loin la meilleure expérience de développement full-stack que j’aie vue jusqu’ici, et avec Zod en particulier, les API deviennent vraiment excellentes
    Je considère Zod et tRPC comme des projets importants pour l’avenir de TypeScript, et je pense que, dans les prochaines années, l’écosystème TypeScript verra largement fleurir des expériences développeur inspirées de tRPC
    Parmi les projets dont l’ADN tRPC est déjà évident, il y a UploadThing de Ping (https://github.com/pingdotgg/uploadthing), qui vise d’autres cas d’usage, ainsi que notre Lusat (https://github.com/lusatai/lusat)

    • Je n’aime pas trop Zod. Les types et paramètres génériques sont délicats et verbeux ; on ne peut pas passer des types de données à des génériques d’objets, il faut passer les types de schéma propres à Zod comme propriétés, ce qui est brouillon et peu intuitif
      Le fait que le type des erreurs de validation varie selon le schéma testé est également pénible. Le traitement des erreurs devient imprévisible, avec toujours trop de cas particuliers
      Il y a beaucoup de marge d’amélioration
    • J’ai récemment utilisé tRPC et Zod dans un projet personnel, et je confirme que l’expérience était vraiment excellente
      L’écriture de tests unitaires est aussi devenue beaucoup plus facile
    • Je me demande s’il existe des outils de ce genre côté MongoDB
      Le gros problème semble être le CRUD type-safe et les migrations de données ; Mongoose est souvent mentionné, mais en matière de sûreté de typage, cela donne l’impression d’un net recul par rapport à Zod/TypeScript
    • Zod est vraiment incroyablement bon
      Là, j’ai envie de forcer un peu le trait, mais pour certains styles de programmation, on peut presque dire qu’ajouter Zod à une base de code apporte autant de bénéfices qu’ajouter TypeScript
    • Je recommande aussi effect/core et effect/schema
      Le pattern qui consiste à partir du schéma pour créer des services typés convient bien aux personnes qui penchent vers la programmation fonctionnelle
  • Je me demande comment tRPC gère les différences de version et les migrations
    Les champs ont un cycle de vie. Lorsqu’ils sont introduits pour la première fois, aucun client ni serveur ne les connaît
    Les clients et les serveurs ne sont pas tous redémarrés en une seule fois, et ils n’ont pas non plus la même version des types
    Les données sont écrites avec une version du type et lues avec une autre
    Ce n’est peut-être pas un problème si l’on peut garantir que tous les binaires, programmes en cours d’exécution et données seront mis à niveau, et s’il n’y a pas de données persistantes
    Mais dès que plusieurs organisations sont impliquées, il devient difficile de garantir que toutes les apps et tous les serveurs sont synchronisés sur la dernière version de la bibliothèque, reconstruits et redéployés
    La vérification statique des types suppose l’absence de différences de version. Tout le code d’un binaire constitue un monde fermé, compilé avec la même version de la bibliothèque qui définit les types

    • En gros, c’est presque « à vous de le faire »
      Zod peut autoriser les champs inconnus, puis on peut passer à des champs optionnels, et enfin les rendre obligatoires une fois la situation stabilisée
      Bien sûr, plus un public a envie que tRPC fasse les choses à sa place, moins il est susceptible de réfléchir à ce genre de problème
    • C’est pour ça que c’est un outil pour applications web
  • Si l’on ne vise qu’un seul langage, il est évident qu’on n’a pas besoin de schéma ni de génération de code

  • J’ai utilisé tRPC dans deux applications web d’environ 50 000 lignes chacune, et je l’aime beaucoup. L’expérience développeur est excellente
    Cela dit, la période de hype de tRPC est passée depuis un bon moment, et celle des RSC semble l’avoir rapidement rattrapée
    Aujourd’hui, même avec une solution aussi stable et bonne que tRPC, tout le monde ne parle que de savoir s’il faut utiliser les RSC ou non
    Je ne suis pas opposé aux RSC, mais il y a trop de discussions à leur sujet. Pour construire des applications aujourd’hui, tRPC reste une approche très pragmatique
    Ajout : RSC signifie React Server Components et apporte avec lui sa propre philosophie de lecture/écriture des données

    • Je n’ai jamais entendu parler de RSC, et ce n’est pas facile à trouver en cherchant. Un lien serait utile
    • Je ne vois pas quel rapport la Royal Shakespeare Company a avec RPC
      Ce serait bien de définir les sigles que tout le monde ne connaît pas, ou d’ajouter un lien
  • J’ai utilisé tRPC et Next.js dans quelques projets personnels, et l’expérience a été bonne.
    Surtout avec des templates préconfigurés comme Create T3 App (https://create.t3.gg/), il est difficile de battre la vitesse d’itération.

    • Je me demande si cela fonctionne désormais correctement avec Next 13 et les composants serveur.
  • Je ne connaissais pas vraiment tRPC jusqu’à ce qu’un développeur avec qui je travaillais en fasse l’éloge, mais une fois que je l’ai découvert, cela m’a paru être une évidence.
    Nous avons construit ensemble une app T3 (tRPC, Next.js, Tailwind, TypeScript, Prisma) ; si vous voulez la voir, elle est ici : https://github.com/stytchauth/stytch-t3-example
    Quand on travaille avec TypeScript, une API type-safe est vraiment d’une grande aide.