tRPC: cool demo, bad reality
Sep 17, 2025
TL;DR;
tRPC promises speed and type safety, but in practice it’s opaque, unscalable, and fragile once projects grow past 3k LOC. Its type inference leads to IDE freezes, debugging nightmares, and hidden API contracts. Instead, explicit codegen with OpenAPI gives you debuggable, versioned, team-friendly contracts that actually scale.
The working title of this devlog series as I conceived of it while zooming to work on Bethnal Green High Street on a LimeBike was "i fucking hate: X". It was early and I want to become a nicer person, so yeah, trpc is not my cup of tea.
Why should you listen to me? For starters, I love tea. I love staying up until 2am drinking the stuff. Lemon, white, black, red, earl gray, lady gray, just gray (with milk) - I be chugging it. I drank a fair few teas during my short but, some may say, illustrious career: I drank the .NET tea, Vue tea, 10 different teas by a company called Facebook, Svelte tea, htmx tea and I even got paid for drinking most of these teas! That is to say there was usually (someone about as sharp as a bowling ball) a product person breathing down my neck telling me stop sipping and start guzzling the tea or we will miss a Very Important Launch (tm).
You will say: "ok boomer" and I will reply - no! not okay! To take a break from the tea analogy that I am enjoying way too much: tRPC is opaque, unscalable and fragile. And I am here to tell you why and what you could do instead.
trpc features
Let's hear Alex Johansson, the creator of tRPC:
I built tRPC to allow people to move faster by removing the need of a traditional API-layer, while still having confidence that our apps won't break as we rapidly iterate.
As a pretty confident fellow I have so far failed to become more so by using tRPC at my job. And of course, the API layer is not removed - it is obfuscated.
tRPC website claims that by using tRPC you can access wonderful features [emojis theirs] such as:
✅ Well-tested and production ready.
🧙♂️ Full static typesafety & autocompletion on the client, for inputs, outputs and errors.
🐎 Snappy DX - No code generation, run-time bloat, or build pipeline.
🍃 Light - tRPC has zero deps and a tiny client-side footprint.
🐻 Easy to add to your existing brownfield project.
🔋 Batteries included - React.js/Next.js/Express.js/Fastify adapters.
🥃 Subscriptions support. ⚡️ Request batching - requests made at the same time can be automatically combined into one. 👀 Quite a few examples that you can use for reference or as a starting point.
well tested and production ready
no doubt about it!
snappy dx
Querio is a next gen BI tool. that is, it's a CRUD app where most of the work happens away from the browser. at Querio, the average tRPC error looks like this:
there is nothing snappy about inferring a very big type, there is no debugging being done on this type error. the only snappy thing here is yours truly - after spending hours hunting for an issue where an inferred type from the backend was different from the type stored in the database by a single property.
i want to stress: this is tRPC's fault. typescript's gradual typing + magicking away the contract at the boundary creates a perfect storm for unwieldy schemas in any project >3kLOC [estimate mine]. when your procedure returns a complex object with nested relations, tRPC happily infers a type that becomes impossible to work with. the compiler chokes, your IDE freezes, and you're left staring at error messages that span multiple screens.
full static typesafety and autocompletion on the client, bla bla bla
see above. the exact same type-safety can be achieved by standard code-gen, where you control the types and support the OpenAPI standard.
easy to add to your existing project
i don't know why someone would want more API layers, especially in a "brownfield" project which is presumably already a rage-baiting place to work. assuming the brownfield project is in typescript. as I have alluded to before, all i can imagine happening is this. the type inference overhead from adding a single trpc procedure overheats your M4 Macbook, causing its screen to explode into hundreds of shards of less-than-liquid Retina display embed themselves between your pores leaving until you look like this guy from Die Another Day.
batteries included
now i have big beef with what "batteries included" has come to mean in the kind of web dev that i (am forced to) do. sometimes it means a rich library of functionality, easy setup, essential components for full functionality. in tRPC's case it means: hey pal don't worry about how a crucial component - the API contract - of your application interacts with the rest of your stack. we magic the whole thing away and when it breaks...well, idk.
"batteries included" should mean you get useful tools, not that fundamental concepts get abstracted away. when your API contract is invisible, you lose the ability to reason about your system's boundaries.
in my view, the frameworkification, the LLMs, the auxiliary libraries like tRPC all add downward pressure on the quality of the average engineer. it's no secret that the labour market for devs is a bubble. bubble so big i can take off into space on it and then jump back down, falling screaming through the air like a mortar shell until the impact ignites the earth's crust causing a fiery inferno that will swallow all the tRPC enjoyers so I can codegen again.
not to mention the additional humiliation of the invisible hand cashing in on the bubble under the fanfare of marketing slogans ("blazing fast!") parroted by programming influencers: i hate all of you. i mean, you’re all not my cup of tea.
of course none of this is directly tRPC's fault. but it's highly complicit in this trend toward obfuscation via inflated incidental complexity. libraries in JS in particular are often crutches, not tools. you are high on libraries! sober up!
🥃 subscriptions support
not sure what subscriptions they are talking about but i definitely need a glass of whisky or two after a long day of working with tRPC.
request batching
is a nice band-aid for the waterfall IO interactions that tRPC inadvertently helps facilitate. assuming you are intentional about how you load data (very big assume and very commonly tweeted about topic) batching requests should never be required. not to mention it breaks debugging.
if not trpc then what
maybe codegen? i will break it down in case you have already suffered from tRPC-itus.
you have a backend application and a frontend application. the frontend application needs to send HTTP requests to the backend. you do not want to do it willy nilly and want it to be strongly typed.
regardless of your language/framework of choice, your router definitions produce an OpenAPI spec.
use your language/framework's native facilities to enhance the spec with the model definitions you want to ship from be to fe: e.g
Product
,Order
, whatever.use one of many OSS tools that turns a
openapi.[yaml|json]
into a collection of classes in the language of your choice.enjoy the freedom to structure your API as you see fit with fully static typing.
nice things you get for free:
your types are explicit instead of inferred
any change to the contract is a git commit with all the safety that comes with that
odds are you are no longer reliant on some guy's personal project for your job (at the very least you rely on one less such project)
conclusion
I do not have a lot of time to write this blog but hopefully you can see the three main ideas:
tRPC and libraries like it enjoy obfuscating complexity away from you that is your job to know. tRPC in particular claims many features which all top out after a project hits a certain size and/or you collaborate with people. this does not happen on accident but because tRPC is fundamentally not a very good idea.
tRPC is another library in a long line of crutches used to prop up an inadequate workforce, educated by below average twitter engineers, with neither respect nor passion nor curiosity for the craft they have adopted
codegen with OpenAPI provides explicit, debuggable contracts that scale with your team and codebase, unlike tRPC's type inference which becomes unwieldy as projects grow
that is to say, tRPC is opaque, unscalable (past a single person or 3kLOC, whichever comes first) and fragile.
i realize now the same judgments can be levied against most other things inside my package.json
- infinite blogs for me!