[HN Gopher] TRPC: End-to-end typesafe APIs made easy
       ___________________________________________________________________
        
       TRPC: End-to-end typesafe APIs made easy
        
       Author : namelosw
       Score  : 106 points
       Date   : 2022-05-06 14:50 UTC (8 hours ago)
        
 (HTM) web link (trpc.io)
 (TXT) w3m dump (trpc.io)
        
       | jeroenhd wrote:
       | Only supports using Javascript/node servers it seems, so sadly
       | it's not really usable for me.
       | 
       | The type exchange seems to rely on shared code between the
       | frontend and the backend to exchange types, rather than using
       | schemas like other systems.
       | 
       | Not implementing server-side events seems like a shame because
       | they're a very capable mechanism for subscriptions without
       | resorting to websockets.
        
         | goodoldneon wrote:
         | If you're using OpenAPI, you could use this to generate
         | TypeScript interfaces:
         | 
         | https://github.com/bcherny/json-schema-to-typescript
         | 
         | It works really well
        
         | chrisarnesen wrote:
         | TRPC is designed for systems where the front and back ends are
         | both TypeScript. In that case it's simpler to have them share
         | code than to go through an intermediary type system such as
         | GraphQL.
        
         | sachinraja wrote:
         | I don't believe there's anything really preventing the use of
         | server-side events. The Websocket implementation is via an
         | adapter and special client so I think the same could be done
         | for SSE (probably achievable in an external package too).
        
       | bradrn wrote:
       | I've never used this, but it looks like a very similar idea to
       | Haskell's Servant library [https://docs.servant.dev/en/stable/].
       | Could someone who knows more enlighten me as to how correct this
       | comparison is?
        
         | Vosporos wrote:
         | Hi, Servant user here: Haskell's type system is sound, which
         | means that you can have type safety. TypeScript's type system
         | is unsound, so every guarantee that you may think you have wrt.
         | type safety go out of the window at the moment you start the
         | typechecker.
        
           | lalaithion wrote:
           | Hey, don't be this way. Haskell's type system is _also_
           | unsound (see: undefined, unsafePerformIO). Typescript has
           | done an amazing job bolting an advanced static type system on
           | top of a dynamically typed language. The edge cases of that
           | type system exist, and are a bit more prevalent than Haskell
           | 's, but there exist a number of libraries in Typescript that
           | take inspiration from Haskell libraries. This question is
           | about the differences between the design of these two ways of
           | statically representing APIs and not about the fundamental
           | differences in the type systems of their host languages.
        
         | aranchelk wrote:
         | They seem similar. With Servant you start with a type
         | definition describing your API (using type-level functions),
         | from their docs:
         | 
         | type UserAPI1 = "users" :> Get '[JSON] [User]
         | 
         | In tRPC based on the example it looks like you start by
         | defining and instance then grab the type and export it.
        
       | arpinum wrote:
       | I'm not seeing the appeal of this over Smithy, which can target
       | multiple frontend and backend languages. What am I missing? What
       | is wrong with codegen tools? Especially when they help you design
       | strong contracts upfront, rather than ad-hoc API design as their
       | demo shows.
        
       | janpot wrote:
       | Nice! I wrote something similar for Next.js a while ago. The
       | pattern is so much more ergonomic than everything else I've used
       | before.
       | 
       | https://npmjs.com/package/next-rpc
        
       | gavinray wrote:
       | You can do this with GraphQL too:
       | 
       | https://genql.vercel.app/
       | 
       | https://github.com/graphql-editor/graphql-zeus
       | 
       | I did a 5 min talk about these newer breeds of codegen tools
       | (where it's a single client SDK that does automatic return type
       | inference based on the input args), they're really neat:
       | 
       | https://www.youtube.com/watch?v=7n3MeMFHiMk
       | 
       | (Skip to 2:14 to see the autocomplete/type-safety)
        
         | [deleted]
        
         | hu3 wrote:
         | It's not the same though. tRPC requires no code generation.
         | 
         | With tRPC if I change the return type of an API, I get instant
         | feedback on my frontend code with errors and autocompletion.
         | 
         | Is my understanding correct?
        
           | sirtimbly wrote:
           | Correct! Instead of codegen, it relies on the TS language
           | server to enforce schema compliance.
        
       | Vosporos wrote:
       | you can't have type-safety with an unsound type system, silly.
        
         | sachinraja wrote:
         | Could you elaborate on this? What part of tRPC do you think is
         | unsound due to the type system?
        
           | colinmcd wrote:
           | TypeScript's type system is unsound in a mathematical sense:
           | https://effectivetypescript.com/2021/05/06/unsoundness/
           | Though this has little bearing on practicality which is
           | probably why the original comment is at the bottom of the
           | pile.
           | 
           | He's also using a stricter mathematical definition of "type
           | safety" whereas tRPC means it in the colloquial way it's used
           | in the TypeScript ecosystem (that is, a fully typed interface
           | between client and server, ideally that's non-duplicative and
           | inferred directly from your code instead of being manually
           | defined).
        
       | markoutso wrote:
       | I am really not getting this. If you run Typescript on both ends,
       | can't you just share the source code that defines the types?
        
         | hu3 wrote:
         | You kinda can share function signatures with TypeScript's
         | import type.
         | 
         | And that's what tRPC does but adds some generics in the middle
         | to make things seamless.
         | 
         | Explanation here: https://colinhacks.com/essays/painless-
         | typesafety
        
           | ricardobeat wrote:
           | You could argue that this is actually adding a lot of (nicely
           | knit) seams.
        
             | colinmcd wrote:
             | I think that's a good characterization!
        
         | colinmcd wrote:
         | Basically tRPC lets you define a router containing all your
         | endpoints in a single structure. Then you can import the type
         | definition of your router and make typed API calls without
         | needing to wire everything together with types.
        
       | colinmcd wrote:
       | I wrote the initial proof of concept for tRPC a little over a
       | year ago (though I'm no longer involved in the project). Seems
       | like there's some confusion about how this works. A more complete
       | description is available here[0] below but I'll put a brief
       | explanation below as well.
       | 
       | - It's designed specifically for a full-stack TypeScript app.
       | Your API is implemented as strongly typed server-side functions.
       | 
       | - The input type is specified using a schema library like Zod or
       | io-ts.
       | 
       | - TypeScript infers the return type of these functions.
       | 
       | - You can then import the type signatures of your functions into
       | your client. Just types, no runtime code! This is safe in
       | typescript with the `import type` syntax. tRPC uses those type
       | signatures to provide a typesafe client API, basically an SDK for
       | your API. There's no codegen happening - the client uses
       | TypeScript generics to provide a set of strongly typed "proxy
       | functions" that can be "called" on the client. These proxy
       | functions execute HTTPs requests under the hood:
       | https://trpc.io/docs/rpc
       | 
       | The point is to avoid the boilerplate, codegen, and lack of
       | DRYness associated with building a strongly typed API in
       | TypeScript. You can do this with GraphQL but it relies on code-
       | generation, requires complete buy-in, and (IMO) doesn't provide a
       | great developer experience. TypeScript has a native way of
       | representing types, so the goal is to avoid an intermediate
       | representation (in the form of an OpenAPI spec or GraphQL schema)
       | entirely. As you update your server code, the client's type
       | signatures update instantaneously.
       | 
       | Another important goal is to make it easy to write DRY code when
       | building APIs. Without tRPC, it's common to manually provide type
       | definitions for the results of API calls. But those definitions
       | can easily get out of sync with your actual server code. tRPC
       | infers types from your code itself and provides a typed interface
       | for your API thats doesn't require any manual type annotations.
       | 
       | [0] https://colinhacks.com/essays/painless-typesafety
        
         | codethief wrote:
         | This is very cool, thanks so much for explaining how this works
         | and, of course, for initiating such a cool project!
         | 
         | As far as I can tell, tRPC basically bridges (hides) network
         | communication and allows doing remote procedure calls almost
         | like regular, type-safe function calls on the same machine
         | within the same process.
         | 
         | Now I would give a fortune for having something like that also
         | for cross-language communication and for communicating with
         | other processes (binaries and shell scripts). I've been
         | thinking about this a lot in recent years: In SWE we need to
         | jump through many hoops only because it's so god damn hard to
         | invoke a function or a whole API living somewhere else in a
         | type-safe manner. Intermediate formats like JSON, protobuf,
         | standards like REST, OpenAPI, and GraphQL, and even languages
         | like SQL (and the various ORM libraries it inspired) are all a
         | result of that to some degree: Once data leaves the safe haven
         | of your (hopefully statically typed) programming language where
         | the compiler/interpreter knows exactly what its type is and
         | what the signature of the function is that you're trying to
         | call, you're basically on your own.
         | 
         | I recently read about WASM Interface Types[0] and it seems like
         | a step in the right direction, though unfortunately it will
         | only bridge the - let's call it - _type safety gap_ between
         | processes, not between different machines in a network.
         | 
         | [0]: https://hacks.mozilla.org/2019/08/webassembly-interface-
         | type...
        
         | oneplane wrote:
         | So essentially, it's protobuf but it describes the API instead
         | of a message (which arguably is also an API description) and is
         | for TypeScript on both sides.
        
           | colinmcd wrote:
           | Exactly. But with faster iteration speed, more type
           | inference, less overhead, and a TypeScript-native DX.
        
         | lalaithion wrote:
         | What's the backwards compatibility story here? What changes to
         | backend function argument and return types can I make without
         | breaking currently-open webpages when I push a new backend
         | instance?
        
       | theobr wrote:
       | tRPC has been a lifesaver for us at Ping.gg
       | 
       | Never had such a seamless experience building and deploying "full
       | stack" apps. Multiple classes of bugs just, like, don't happen
       | now?
       | 
       | Helped build up a lot of the GraphQL tech at twitch, and I still
       | love it, but man do I not miss all the work maintaining the
       | "contract" between front and back. I know we will have to move
       | off tRPC eventually, but I'm hyped at how far it's taken us
       | already
        
         | hu3 wrote:
         | > I know we will have to move off tRPC eventually
         | 
         | Is that when you can no longer get by with only
         | NodeJS/TypeScript on the backend?
         | 
         | I've been thinking about that in a greenfield project I'm
         | trying tRPC out.
        
       | hegem0n wrote:
       | I love this. This is the future
        
       | jre wrote:
       | I like this idea very much. Shameless plug and I don't want to be
       | the Rust fanboy, but I've played with something similar in Rust:
       | 
       | https://github.com/julienr/liveboard-rs
       | 
       | Basically it uses actix for the backend and yew (Vue-like rust
       | frontend framework) for the frontend. This enables one to share
       | types (and helper functions) between both, which is great:
       | 
       | https://github.com/julienr/liveboard-rs/blob/master/shared/s...
       | 
       | That being said, I think maturity-wise, Typescript is probably a
       | better bet for this right now, so I'll definitely look at trpc
       | for $dayjob.
        
       | eandre wrote:
       | If you want a similar experience to this but more optimized for
       | backend development using Go, I've been building
       | https://github.com/encoredev/encore for the past few years!
        
       | tantalor wrote:
       | > without schemas
       | 
       | What is a type definition if not a schema?
        
         | sirtimbly wrote:
         | Conceptually, yes. But type definitions are exported by
         | declarative lines in the same code that runs. Basically the
         | schema is imperative code that runs actual validations, the
         | tRPC methodology simply looks at the inferred types from that
         | code to enforce consistency between client and server rather
         | than parsing an actual static schema artifact (e.g. a .gql
         | file).
        
       | kristiandupont wrote:
       | I recently gave a talk about our experience with tRPC. So far
       | it's looking very promising!
       | 
       | https://www.youtube.com/watch?v=k1TCueEhhJo
        
       | evantahler wrote:
       | This is one of the best things about typescript. You can do this
       | kind of thing without much special tooling at all -
       | https://www.evantahler.com/blog/post/2020-10-16-typescript-f...
        
       | lowwave wrote:
       | interesting. Kinda like fastify json schema.
        
       | [deleted]
        
       | vander_elst wrote:
       | How does this compare to gRPC? That the wire protocol is actually
       | http+json instead of binary? I wasn't able to find much on their
       | website
        
         | sachinraja wrote:
         | tRPC offers typesafety across your frontend and backend without
         | needing to generate any code. This makes it so updates in your
         | backend "router" are immediately reflected in your frontend
         | code. You can't use gRPC with TypeScript without codegen. tRPC
         | may be more familiar to TS/Node.js devs too.
        
       | awhitty wrote:
       | This project looks interesting! I have used a similar project
       | called tsoa [1] and have appreciated that it generates an OpenAPI
       | spec that can be consumed readily by many other clients, even
       | outside the JS ecosystem. Does TRPC have plans for a similar
       | feature set?
       | 
       | [1] https://tsoa-community.github.io/docs/
        
         | alexdotjs wrote:
         | Yup, with the optional `output`-property we actually have all
         | the type information needed to generate an openapi-schema.
         | 
         | See this for more info:
         | https://github.com/trpc/trpc/issues/1724
        
       | mhoad wrote:
       | Not looking to actively bash anyones work here but I'm really
       | confused on the value proposition here over something like gRPC
       | which has a lot of substantial advantages from speed, to wide
       | cross language support and has been battle tested in the most
       | extreme scaling scenarios possible.
       | 
       | A truly huge amount of time, money and optimization went into a
       | project like that and has been proven to work well for years now.
       | 
       | The JS ecosystem confuses me with things like this regularly just
       | the other day I was watching a video where the lead devrel person
       | from Vercel was trying to explain "edge functions" to a senior
       | backend engineer that started falling apart under the most
       | rudimentary questioning so much so that in the video they have
       | this awkward hard cut and they rush to wrap up the interview.
       | Link if anyone is interested https://youtu.be/yuxd2kurpzk
       | 
       | This gives me kind of similar vibes in that I just don't really
       | get the use case here unless it's to intentionally stay strictly
       | in the JS ecosystem for some reason?
        
       | stickfigure wrote:
       | The docs don't say anything about the wire protocol - is this
       | based on REST/JSON, or something proprietary? If proprietary, is
       | it spec'd and documented? Support for implementations in other
       | languages?
        
         | tcbyrd wrote:
         | https://trpc.io/docs/rpc
        
       | alexdotjs wrote:
       | Awesome to see my lil' baby on the frontpage!
        
       ___________________________________________________________________
       (page generated 2022-05-06 23:01 UTC)