- 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 undroutersowiepublicProcedurekonfiguriert - Die Prozedur
greetingnimmt einen Stringnameals Eingabe entgegen und gibt einen String in der FormHello ${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
- Im Beispiel wird mit
-
HTTP-Server erstellen
- Durch Übergabe von
appRouterancreateHTTPServerwird der tRPC-Server gestartet - Im Beispiel wird mit
listen(3000)festgelegt, dass die API auf Port3000lauscht - 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
- Durch Übergabe von
-
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
httpBatchLinkmithttp://localhost:3000 - Mit einem Aufruf wie
trpc.greeting.query({ name: 'John' })lässt sich die Server-Prozedurgreetingverwenden - Wird beim Erstellen des Clients der Typ
AppRouterübergeben, erhält man ohne Codegenerierung TypeScript-Autovervollständigung und IntelliSense, die zur Backend-API passen
- Sobald der Server läuft, kann mit
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
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
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
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
Dafür gibt es eine Lernkurve, und meistens fängt sie mit nutzlosen, nichts erklärenden Typen wie
type FunctionIWroteTodayArgs = …anNach 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
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
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 einPromisezurückgibt; der Client stellt ihn als asynchrone Funktion mit denselben Argument- und Rückgabetypen bereitWir 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
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
Ich glaube, darüber wird nicht oft diskutiert, weil es noch keine gut gepflegte und gut vermarktete Library gibt, die diesen Ansatz verfolgt
Genau darin liegt ein wichtiger Wert, wenn man tRPC, JSON Schema plus Typgenerierung oder etwas wie Zod verwendet
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
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
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
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
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)
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
Auch das Schreiben von Unit Tests wurde viel einfacher
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
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
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
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
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
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 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.