2 Punkte von GN⁺ 2023-08-13 | 1 Kommentare | Auf WhatsApp teilen
  • tRPC ist ein Werkzeug für Full-Stack-TypeScript-Apps, das TypeScript-Inferenz gemeinsam nutzt, um Abweichungen bei API-Typen zwischen Server und Client zu verringern
  • Wenn sich die Server-API ändert, lassen sich die Auswirkungen im Client-Code sofort über TypeScript-Fehler und Autovervollständigung erkennen, was den Refactoring-Aufwand reduziert
  • Es funktioniert ohne separates Schema oder Codegenerierungs-Schritt, sodass sich typsichere APIs ohne zusätzliche Build-Prozesse oder Runtime-Overhead aufbauen lassen
  • Es bietet Adapter für viele JavaScript-Umgebungen wie React, Next.js, Express, Fastify, AWS Lambda, Solid und Svelte
  • Der grundlegende Ablauf besteht aus dem Definieren von Prozeduren, dem Erstellen eines HTTP-Servers und dem Verbinden des Clients mit dem Typ AppRouter; Ziel ist eine Developer Experience, bei der sich der API-Server-Code wie ein SDK nutzen lässt

Wie tRPC die API-Entwicklung verändert

  • tRPC steigert die Produktivität in Full-Stack-Anwendungen, indem Server und Client dieselben TypeScript-Typinformationen nutzen
  • Im Kern geht es darum, End-to-End-typsichere APIs einfach zu erstellen und wiederkehrende Arbeit in traditionellen API-Schichten zu reduzieren
  • Wenn Änderungen auf der Serverseite die Client-Nutzung betreffen, meldet TypeScript Fehler, sodass sich Typabweichungen an der Client-Server-Grenze schnell erkennen lassen

Wichtige Merkmale aus Entwicklersicht

  • Automatische Typsicherheit

    • Änderungen auf der Serverseite, die sich auf Client-Code auswirken, werden als TypeScript-Fehler sichtbar
  • Schnelle Developer Experience

    • tRPC hat keinen Build- oder Kompilierungsschritt
    • Es kann ohne Codegenerierung, Runtime-Overhead oder separate Build-Prozesse verwendet werden
  • Framework-Unabhängigkeit

    • Es lässt sich über JavaScript-Frameworks und Runtimes hinweg einsetzen
    • Es kann auch bestehenden Projekten leicht hinzugefügt werden
  • Autovervollständigung

    • Es bietet eine Erfahrung, bei der sich API-Server-Code wie ein SDK behandeln lässt
    • Bei der Nutzung von Endpunkten erhält man typbasierte Hinweise
  • Kleine Client-Footprint

    • tRPC hat keine Abhängigkeiten und ist auf der Client-Seite sehr schlank
  • Enthaltene Adapter

    • Es bietet Adapter für React, Next.js, Express, Fastify, AWS Lambda, Solid und Svelte

Grundlegender Nutzungsablauf

  • Eine tRPC-API beginnt damit, zunächst Prozeduren (procedures) zu definieren
  • Prozeduren sind Funktionen, aus denen das Backend besteht; sie sind kombinierbar und können als Query, Mutation oder Subscription erstellt werden
  • Mehrere Prozeduren werden in einem Router gebündelt
  • Prozeduren definieren

    • Im Beispiel wird mit initTRPC.create() eine tRPC-Instanz erstellt und router sowie publicProcedure konfiguriert
    • Die Prozedur greeting nimmt einen String name als Eingabe entgegen und gibt einen String in der Form Hello ${input.name} zurück
    • Für die Eingabevalidierung wird Zod verwendet, damit die Client-Eingabe exakt der Form entspricht, die die Prozedur erwartet
    • Am Ende der Datei wird mit export type AppRouter = typeof appRouter; der Router-Typ exportiert, damit er im Frontend-Code verwendet werden kann
  • HTTP-Server erstellen

    • Durch Übergabe von appRouter an createHTTPServer wird der tRPC-Server gestartet
    • Im Beispiel wird mit listen(3000) festgelegt, dass die API auf Port 3000 lauscht
    • tRPC bietet Adapter für Next.js, Express, Fetch-API-basierte Umgebungen, Fastify, AWS Lambda und einen Vanilla-Node-HTTP-Server
    • Beispiele für Fetch-API-basierte Umgebungen sind Astro, Remix, SvelteKit und Cloudflare Workers
  • Client verbinden und Query ausführen

    • Sobald der Server läuft, kann mit createTRPCClient<AppRouter> ein Client erstellt und Daten abgefragt werden
    • Der Beispiel-Client verbindet sich über httpBatchLink mit http://localhost:3000
    • Mit einem Aufruf wie trpc.greeting.query({ name: 'John' }) lässt sich die Server-Prozedur greeting verwenden
    • Wird beim Erstellen des Clients der Typ AppRouter übergeben, erhält man ohne Codegenerierung TypeScript-Autovervollständigung und IntelliSense, die zur Backend-API passen

Startvorlagen und Zielsetzung

  • Als Vorlage zum direkten Ausprobieren steht Use this template bereit
  • Der Entwickler von tRPC hat tRPC geschaffen, um den Bedarf an traditionellen API-Schichten zu verringern und schnelles iteratives Entwickeln zu ermöglichen, ohne dass die Anwendung dabei kaputtgeht
  • tRPC wird von führenden Technikteams und mehreren Fortune-500-Unternehmen eingesetzt

Reaktionen von Entwicklern

  • In mehreren Entwicklerzitaten wird tRPC als Werkzeug bewertet, das Codequalität, Auslieferungsgeschwindigkeit, Entwicklerzufriedenheit, Refactoring an der Client-Server-Grenze, Eingabevalidierung und die Erfahrung mit typisierter Middleware verbessert
  • In TypeScript-Monorepos wird es als Option wahrgenommen, die einfacher als plain REST oder GraphQL ist und dennoch starke Typisierung bietet
  • Es wird ein Anwendungsfall vorgestellt, bei dem selbst dann, wenn Stripe-API-Payloads vom Server zurückgegeben werden, React-Komponenten weiterhin die Typen der Antwortdaten erhalten

1 Kommentare

 
GN⁺ 2023-08-13
Meinungen auf Hacker News
  • Wir sind gerade dabei, tRPC aus unserer Codebasis zu entfernen, und wegen der starken Kopplung war es ein Albtraum
    Es hatte auch den Effekt, Junior-Entwickler davon abzuhalten, über Interfaces oder Datenzugriffsmuster nachzudenken, und es entstand ein Mapping, das direkt von Prisma bis zu den Komponenten durchgereicht wurde
    Für schnelles Prototyping ist es großartig, aber wenn man die Codebasis später auftrennen will, landet man sehr schnell in einer Sackgasse

    • Genau das ist ein übersehener Vorteil von Schemas. Schemas wie bei GraphQL zwingen einen, über Datentypen und Verträge nachzudenken, und erleichtern es Leuten, die an anderen Teilen des Codes arbeiten, sich darauf abzustimmen
      Weil sie auch auf Sprachen außerhalb von TypeScript erweiterbar sind, helfen sie auch, wenn man das Backend in eine andere Sprache verlagert oder native Mobile-Clients in Swift oder Kotlin baut
    • Wir haben in der Firma tRPC eingeführt, und mit ein wenig Vorab-Design trennen wir die Dinge, die im Code „stark gekoppelt“ sein könnten, recht sauber voneinander
      tRPC ist großartig, aber letztlich nur die Transportschicht zwischen Backend und Frontend
      tRPC-interne Strukturen tief in die Business-Logik einsickern zu lassen, ist genauso schlecht wie keine Controller- oder Router-Schicht zu haben, die Inputs, Schemas und Trennung klar definiert
      Deshalb dürfte es später relativ einfach sein, tRPC wieder zu verlassen; zum Beispiel sollte es auch nicht schwer sein, ein ganzes Subsystem so zu verschieben, dass es über eine Queue läuft
    • Das Problem ist weniger tRPC, sondern eher, dass es Engineers gibt, die Typen nur verwenden, um Typen dranzukleben. Mit jedem Tool würde dasselbe Problem auftreten
      Dafür gibt es eine Lernkurve, und meistens fängt sie mit nutzlosen, nichts erklärenden Typen wie type FunctionIWroteTodayArgs = … an
      Nach einigen Iterationen merkt man allmählich, dass das Ziel nicht ist, Code zu duplizieren, sondern die Domäne zu beschreiben und wiederverwendbare, hilfreiche Typen, Interfaces und APIs zu schaffen
      Deshalb denke ich, dass man statt tRPC herauszureißen lieber gemeinsam mit dem Team genau diesen Teil verbessern sollte
    • Es wäre gut, wenn du genauer erklären könntest, warum die Kopplung ein Albtraum ist
      Ich verstehe nicht ganz, warum es schlimmer sein soll, serverseitig einen HTTP-Client zu deklarieren und ihn clientseitig zu konsumieren
      Wir bauen Services und nutzen ein Muster, das alle Consumer wie Web-Interfaces oder CLIs verwenden; dass diese Dinge nicht kaputtgehen, war eine größere Verbesserung als alles, was ich früher gesehen habe
    • In kleinen Teams mache ich genau die gegenteilige Erfahrung, und ich glaube, es hätte auch in früheren großen Teams gut funktioniert
      Die Beschwerde, dass Junior-Entwickler nicht über Interfaces nachdenken, würde vermutlich bei jeder API genauso auftauchen, nicht nur bei tRPC
  • Bei Notion verwenden wir einen API-Stil, der tRPC ähnelt, und diese API gab es etwa vier Jahre vor tRPC
    Mit den Mapped Types von TypeScript kann man so etwas leicht selbst bauen. Man erstellt einen Objekttyp, bei dem die Schlüssel die API-Namen sind und die Werte Typen der Form { request, response }
    Serverseitig definiert man jeden API-Handler als Funktion, die APIs["addUser"]["request"] entgegennimmt und ein Promise zurückgibt; der Client stellt ihn als asynchrone Funktion mit denselben Argument- und Rückgabetypen bereit
    Wir verwenden diese Strategie für interne HTTPS-APIs, WebSocket-basierte Echtzeit-APIs, IPC zwischen Electron und Webview sowie OS-Webview-IPC zwischen iOS/Android-Native und Webview
    Bei nativen APIs ist die Serverseite in Swift oder Kotlin, daher schreiben wir die Request-/Response-Typen in TypeScript von Hand nach. Irgendwann werden wir auf ein Binärformat mit eigener IDL umsteigen, aber für eine langsam wachsende, einzelne sprachübergreifende API schienen die Developer-Experience-Kosten von etwas wie Protobuf bisher noch nicht lohnend

    • Mein aktuelles Projekt kombiniert ein TypeScript-Frontend mit einem Python-Backend und nutzt ein OpenAPI-Schema als Source of Truth; clientseitig verwenden wir openapi-typescript-codegen [0]
      Es ist nicht perfekt, bietet aber ein ziemlich gutes API-Interface mit typisierten Requests und Responses
      Außerdem habe ich einen 10-Zeilen-Mock-API-Wrapper gebaut, den man wie mockApi((request) => response) aufrufen kann; er prüft per Typprüfung, ob die Mock-Funktion die API korrekt implementiert, und gibt eine Funktion zurück, die genauso aussieht wie die echte API-Funktion
      [0]: https://github.com/ferdikoomen/openapi-typescript-codegen
    • Dieser Ansatz ist richtig. tRPC fügt unnötige Komplexität hinzu, statt einfach nur Typen abzuleiten
      Ich glaube, darüber wird nicht oft diskutiert, weil es noch keine gut gepflegte und gut vermarktete Library gibt, die diesen Ansatz verfolgt
    • Mich würde interessieren, ob es in diesem Prozess einen Kniff dafür gibt, wie man Request-Daten validiert
      Genau darin liegt ein wichtiger Wert, wenn man tRPC, JSON Schema plus Typgenerierung oder etwas wie Zod verwendet
    • Beim Blick auf den Beispielcode frage ich mich, ob das eher ein typisierter Wrapper über XHR-Aufrufen ist
      Es wird sogar verlangt, beim Aufruf die generischen Argumente direkt anzugeben, was ziemlich unschön ist, wenn man versucht, den Dependency Tree sauber zu halten
    • Dieser Ansatz funktioniert in der Praxis ziemlich gut
      https://github.com/mikew/transmission-material-ui/blob/maste...
      https://github.com/mikew/transmission-material-ui/blob/maste...
  • Ich mag tRPC sehr. Es ist beeindruckend, wie weit es in einem reinen TypeScript-Stack die Developer Experience ausreizt, und es hat die GraphQL-Community dazu gebracht, die Grenzen und Kompromisse einer Query Language wahrzunehmen
    Gleichzeitig ist tRPC wirklich sehr schnell durch den Hype-Zyklus gegangen, und es sieht nicht so aus, als gäbe es eine große Bewegung von REST und GraphQL hin zu RPC
    Allerdings scheint es derzeit viel Interesse an RPC zu geben, und auch in unserem BFF-Framework (https://wundergraph.com/) haben wir einige Ideen von tRPC und dem früheren NextJS übernommen und filebasiertes Routing mit RPC kombiniert
    Zusätzlich zu tRPC erzeugen wir für jede Operation automatisch JSON Schema und auch eine OpenAPI-Spezifikation für die gesamte Menge an Operationen
    Leute mögen diesen Ansatz, weil man ein Bündel von RPC-Endpunkten leicht als OpenAPI-Spezifikation oder Postman Collection teilen kann. Es gibt auch kaum Diskussionen über HTTP-Verben; praktisch gibt es nur Queries, Mutations und Subscriptions
    Mich würde interessieren, wie ihr heutzutage GraphQL-, REST- und RPC-artige APIs nutzt und wie viele Personen oder Teams an einer API beteiligt sind

    • Garph ist so etwas wie tRPC für GraphQL: https://garph.dev
      Für REST-APIs gibt es ts-rest (https://ts-rest.com), zodios (https://www.zodios.org) und Hono (https://hono.dev)
      Wenn ihr im Team mehrere Sprachen verwendet, gibt es auch Fern: https://www.buildwithfern.com
    • Es wäre gut, wenn du den Teil „hat die GraphQL-Community dazu gebracht, die Grenzen und Kompromisse einer Query Language wahrzunehmen“ näher erklären könntest
      Wir generieren GraphQL-Typen, wann immer sich das föderierte Schema ändert, und generieren die Response-Typen für Queries und Mutations jedes Mal, wenn eine Datei gespeichert wird
    • Was mir immer auffällt, ist, dass Verben und Pfade ziemlich nebensächliche Details sind
      Mit einer Bibliothek zur Erzeugung von Endpoint-Consumern lassen sie sich leicht abstrahieren, aber darüber hinaus weiß ich nicht, was man gegenüber einer normalen Web-API tatsächlich zusätzlich gewinnt
      Am Ende muss jeder Backend-Aufruf ohnehin die gleiche Arbeit erledigen
  • Ich mag tRPC. Es bietet mit Abstand die beste Fullstack-Developer-Experience, die ich bisher gesehen habe, und besonders zusammen mit Zod werden APIs wirklich hervorragend
    Zod und tRPC halte ich für wichtige Projekte in der Zukunft von TypeScript, und ich denke, dass in den nächsten Jahren im gesamten TypeScript-Ökosystem viel von tRPC inspirierte Developer Experience aufblühen wird
    Projekte, in denen die DNA von tRPC bereits klar erkennbar ist, sind etwa Pings UploadThing (https://github.com/pingdotgg/uploadthing), das andere Use Cases adressiert, und unser Lusat (https://github.com/lusatai/lusat)

    • Zod mag ich nicht besonders. Die Typen und generischen Parameter sind sperrig und wortreich, man kann bei Objekt-Generics keine Datentypen übergeben, sondern muss Zods eigene Schema-Typen als Properties übergeben, was unordentlich und wenig intuitiv ist
      Außerdem ist es lästig, dass sich der Typ von Validierungsfehlern danach richtet, welches Schema geprüft wird. Fehlerbehandlung wird dadurch unvorhersehbar, und es gibt immer zu viele Sonderfälle
      Da gibt es viel Raum für Verbesserungen
    • Ich habe kürzlich in einem persönlichen Projekt tRPC und Zod verwendet und stimme zu, dass es eine wirklich hervorragende Erfahrung war
      Auch das Schreiben von Unit Tests wurde viel einfacher
    • Ich frage mich, ob es im MongoDB-Umfeld Tools für diesen Bereich gibt
      Das große Problem scheinen typsicheres CRUD und Datenmigrationen zu sein; Mongoose wird zwar oft erwähnt, fühlt sich in Sachen Typsicherheit aber wie ein deutlicher Rückschritt gegenüber Zod/TypeScript an
    • Zod ist wirklich unglaublich gut
      Ich bin gerade in der Stimmung, etwas zu übertreiben, aber für bestimmte Programmierstile kann man fast sagen, dass das Hinzufügen von Zod zu einer Codebase einen ähnlich großen Nutzen bringt wie das Hinzufügen von TypeScript
    • Ich empfehle auch effect/core und effect/schema
      Das Muster, ausgehend von einem Schema typisierte Services zu bauen, passt gut zu Leuten mit einer Neigung zur funktionalen Programmierung
  • Ich frage mich, wie tRPC mit Versionsunterschieden und Migrationen umgeht
    Felder haben einen Lebenszyklus. Wenn ein Feld neu eingeführt wird, kennt es zunächst weder ein Client noch ein Server
    Clients und Server werden nicht alle auf einmal neu gestartet und haben auch nicht dieselbe Version der Typen
    Daten werden mit einer Version eines Typs geschrieben und mit einer anderen Version eines Typs gelesen
    Wenn man garantieren kann, dass alle Binaries, laufenden Programme und Daten aktualisiert werden, und es keine persistenten Daten gibt, ist das vielleicht kein Problem
    Sobald aber mehrere Organisationen beteiligt sind, lässt sich schwer garantieren, dass alle Apps und Server auf die neuesten Bibliotheksversionen synchronisiert, neu gebaut und neu deployt werden
    Statische Typprüfung geht davon aus, dass es keine Versionsunterschiede gibt. Der gesamte Code in einem Binary ist eine geschlossene Welt, die mit derselben Version der Bibliothek gebaut wurde, welche die Typen definiert

    • Es läuft fast auf „das musst du selbst machen“ hinaus
      Zod kann unbekannte Felder erlauben; danach kann man zu optionalen Feldern übergehen und sie, wenn sich die Lage stabilisiert hat, zu Pflichtfeldern machen
      Natürlich ist ausgerechnet die Zielgruppe, die am meisten will, dass tRPC so etwas übernimmt, auch diejenige, die am wenigsten über solche Probleme nachdenkt
    • Deshalb ist es ein Tool für Web-Apps
  • Wenn man nur eine einzige Sprache adressiert, ist es selbstverständlich, dass man kein Schema und keine Codegenerierung braucht

  • Ich habe tRPC in zwei Webanwendungen mit etwa 50.000 Zeilen verwendet und mag es sehr. Die Developer Experience ist ausgezeichnet
    Allerdings ist die Hype-Phase von tRPC schon seit einiger Zeit vorbei, und der RSC-Hype scheint sie schnell eingeholt zu haben
    Heutzutage reden alle nur noch darüber, ob man RSC verwenden soll oder nicht, obwohl es mit tRPC eine stabile und gute Lösung gibt
    Ich bin nicht gegen RSC, aber es gibt zu viele Diskussionen darüber. Beim Bau moderner Anwendungen ist tRPC ein sehr pragmatischer Ansatz
    Zusatz: RSC steht für React Server Components und bringt eine eigene Philosophie für lesende und schreibende Datenzugriffe mit

    • Ich habe noch nie von RSC gehört, und es lässt sich auch schlecht danach suchen. Ein Link wäre hilfreich
    • Ich weiß nicht, was die Royal Shakespeare Company mit RPC zu tun haben soll
      Abkürzungen, die nicht jeder kennen könnte, sollte man definieren oder verlinken
  • Ich habe tRPC und Next.js in einigen privaten Projekten ausprobiert und damit gute Erfahrungen gemacht.
    Besonders wenn man es zusammen mit einem vorkonfigurierten Template wie Create T3 App (https://create.t3.gg/) nutzt, ist das Iterationstempo schwer zu schlagen.

    • Ich frage mich, ob es inzwischen auch mit Next 13 und Server Components richtig funktioniert.
  • Ich kannte tRPC nicht, bis ein Entwickler, mit dem ich zusammengearbeitet habe, es in höchsten Tönen gelobt hat. Nachdem ich es kannte, wirkte es aber ganz offensichtlich richtig gut.
    Wir haben gemeinsam eine T3-App gebaut (tRPC, Next.js, Tailwind, TypeScript, Prisma); falls ihr sie sehen möchtet, hier ist sie: https://github.com/stytchauth/stytch-t3-example
    Beim Arbeiten mit TypeScript sind typsichere APIs wirklich eine große Hilfe.