This episode explores tRPC's history from its classical RPC roots to its rise in the TypeScript community, the problems it solves compared to REST and GraphQL, and when tRPC becomes the right choice for internal applications, monorepos, and rapid prototyping.

Before writing code, we need to understand why tRPC exists. Every technology is born from frustration with the old ways — and tRPC is the answer to two decades of frustration: duplicate APIs, clients that have no idea what shape of data the server sends, and tooling that requires codegen.
Episode 1 takes you through tRPC's history, the problems it solves, its comparison with REST and GraphQL, and the scenarios where it is most appropriate. By understanding the context, you will know when to use tRPC and when not to.
RPC is actually not a new concept. Since the era of CORBA and XML-RPC, programmers have been calling remote functions by name. The problem was that those old mechanisms were heavy, rigid, and not type-safe. In the modern world, REST and GraphQL answered different problems, but both still separate server and client at the type level.
tRPC began around 2021 by Alex Johansson, known as KATT in the TypeScript community. The initial idea was simple: if the server and client live in a single TypeScript project, why not leverage the compiler to infer types from the server directly to the client? No separate schema, no generators, no duplication.
Early versions of tRPC were simple RPC libraries based on observables. The progression has been significant:
links, initTRPC, and createTRPCReact, plus full React Query support.router.merge, createCallerFactory, and strengthened Next.js App Router and serverless support.This evolution leads toward one goal: full-stack TypeScript with zero-boilerplate, where adding a single procedure automatically becomes available on the client with full types.
In REST, the shape of the data is determined by the URL and method, but the response type is only known from documentation or manual inspection. An example of an error-prone REST pattern:
curl https://api.example.com/users/1
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"nama": "Arman"}'Nothing guarantees that the response from GET /users/1 will return a nama field. Developers have to keep things consistent manually, and the moment one field changes, the client can silently break. tRPC eliminates this problem because procedure types are inferred directly from the server.
GraphQL solves the overfetch and underfetch problems, but it requires a separate SDL schema, resolvers that must be kept consistent with the schema, and client codegen like GraphQL Code Generator. Every schema change triggers a regeneration of client types.
tRPC uses a validator like zod as the single source of truth, without another schema language, without resolver boilerplate, and without a codegen step. The trade-off: GraphQL excels at public APIs consumed by many different clients, while tRPC excels at servers and clients managed by the same team.
With tRPC, all types live in a single TypeScript codebase. The client receives exactly the same types as the server, and the editor shows autocompletion for procedure names, input shapes, and response shapes:
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
import type { AppRouter } from "./server/router";
const trpc = createTRPCProxyClient<AppRouter>({
links: [httpBatchLink({ url: "http://localhost:3000/trpc" })],
});
const user = await trpc.user.byId.query({ id: 1 });Misspelling a procedure name like trpc.user.byID.query({ id: 1 }) will be rejected by the compiler. Sending the wrong fields will also be rejected before the application runs. This is end-to-end type-safety that REST or GraphQL do not have without additional tooling.
Because there is no generate step, adding features becomes very fast: define a procedure on the server, and the client immediately has it. No schema synchronization, no stale documentation, and no extra build step.
A single change on the server can be tested directly from the editor. The compiler gives instant feedback: if you delete a procedure that the client still uses, an error appears where it is used. Feedback like this is impossible to get from an API whose files are split apart.
To keep your judgment balanced, know tRPC's limitations:
These limitations do not mean tRPC is bad — they just mean it has a clear place, just as REST and GraphQL each have theirs.
tRPC is not a replacement for every API. The most appropriate scenarios:
Conversely, for public APIs consumed by third parties, REST with OpenAPI or GraphQL remains more suitable because their tooling and documentation ecosystems are mature. Episode 16 will discuss integrating tRPC with Next.js and serverless to close this gap.
Info
tRPC and REST are not enemies. Many production apps combine both: tRPC for internal communication between services in a monorepo, and OpenAPI for public endpoints.
Episode 1 opens up the big context behind tRPC: born from the frustration of type duplication, evolving from a simple RPC into a full-stack framework, and offering end-to-end type-safety that REST or GraphQL do not have without additional tooling.
Key takeaways:
In the next episode, episode 2, we will break down tRPC's core concepts and main architecture — how types are inferred behind the scenes, the role of router, procedure, input, output, and caller, as well as the HTTP, WebSocket, and serverless transport adapters.