How tRPC gives TypeScript apps end-to-end type safety, where it fits next to gRPC and REST, and when to reach for something else.
tRPC became popular for one reason: in a TypeScript app, it lets the frontend call the backend as if it were a local function, with full type safety and no schema to maintain. Change a procedure's input on the server and the client fails to compile until you fix the call site. For a full-stack TypeScript team, that removes a whole category of API drift.
It also leaves questions that come up as soon as an app grows. Is tRPC a framework or a library? How does it compare with gRPC, which also calls itself RPC? When is plain REST the better choice? This guide covers what tRPC is and how it works, compares it with gRPC and REST, and introduces a fourth option, Encore, a TypeScript and Go backend framework that offers typed APIs with generated clients and also provisions the infrastructure your backend uses.
tRPC (TypeScript Remote Procedure Call) is an open-source library for building type-safe APIs in TypeScript. On the server you define procedures, grouped into a router. On the client you import only the router's type, and tRPC uses TypeScript's inference to type every call. There is no IDL, no OpenAPI file, and no code generation step.
// server/router.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
export const appRouter = t.router({
userById: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.user.findUnique({ where: { id: input.id } });
}),
createUser: t.procedure
.input(z.object({ email: z.string().email(), name: z.string() }))
.mutation(async ({ input }) => {
return db.user.create({ data: input });
}),
});
export type AppRouter = typeof appRouter;
// client.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server/router';
const trpc = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: '/api/trpc' })],
});
// Typed end to end: a wrong field name or input type is a compile error.
const user = await trpc.userById.query({ id: '42' });
Because the client depends on the server's type, tRPC works best when both live in the same TypeScript codebase, typically a monorepo or a Next.js app.
tRPC is often called a framework, but it's closer to a typed API layer. It decides how procedures are defined, validated, and called, and it stops there. Everything else a backend needs comes from the host and the libraries around it:
That narrow scope is a strength for a full-stack app that already has a home for those pieces, such as a Next.js app on Vercel with a managed database. It becomes a gap when the backend grows into several services with their own databases, queues, and scheduled work.
Both are remote procedure call systems, but they target different problems.
Choose gRPC when services in several languages need a strict contract and efficient binary transport, or you need bidirectional streaming. Choose tRPC when the frontend and backend are both TypeScript and you'd rather not maintain a schema or a code generation step. For more on the transport side, see gRPC vs JSON for microservices.
REST models an API as resources addressed by URLs and manipulated with HTTP methods. tRPC models it as named procedures you call. The practical differences:
REST stays the better default for public APIs, mobile apps written in Swift or Kotlin, and anything third parties consume. tRPC wins on developer speed inside a single TypeScript codebase. If you're weighing REST against another option, our GraphQL vs REST guide covers the third common choice.
tRPC's design choices have costs that show up as a system grows:
Encore keeps what developers like about tRPC, type-safe APIs with no hand-written client code, and extends it to the whole backend. You define endpoints as typed functions, and Encore generates clients for your frontend from the API definitions. Unlike tRPC, it can also generate a Go client or an OpenAPI spec, so clients that aren't written in TypeScript are covered too.
import { api } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";
const db = new SQLDatabase("users", { migrations: "./migrations" });
interface User {
id: string;
email: string;
name: string;
}
export const get = api(
{ method: "GET", path: "/users/:id", expose: true },
async ({ id }: { id: string }): Promise<User> => {
const user = await db.queryRow<User>`SELECT id, email, name FROM users WHERE id = ${id}`;
if (!user) throw new Error("not found");
return user;
},
);
# Generate a typed client for your frontend from the running app
encore gen client my-app --output=./frontend/client.ts
Requests are validated against the TypeScript types, and the same declarations drive the rest of the backend:
encore run, and are provisioned automatically in your own AWS or GCP account.Encore is open source, with over 12,000 GitHub stars, and is used in production across hundreds of companies, including Coinbase (Echo) and Groupon. For a deeper comparison, see tRPC vs Encore.ts.
Run in your terminal to get started locally.
Start at the top and follow the first question you can answer with a yes:
Do you want typed APIs plus infrastructure provisioned from your code, without building a DevOps team?
Are you building several services that share databases, Pub/Sub, or cron jobs?
Do services in several languages need a strict contract and binary transport, or bidirectional streaming?
Will mobile apps, other languages, or third parties call your API?
Is it a full-stack TypeScript app with the frontend and backend in one codebase, such as Next.js?
In more detail:
tRPC is the fastest path for a full-stack TypeScript app where one team owns the frontend and backend, and the backend's infrastructure already has a home, for example Next.js with a managed database.
gRPC fits backends made of services in different languages that need strict contracts, efficient transport, and streaming.
REST remains the standard for public APIs, mobile clients, and integrations, where any client should be able to call the API and HTTP caching matters.
Encore fits when the backend is more than an API layer: several services, databases, queues, and scheduled jobs that you want defined in code, tested locally, and deployed to your own cloud, with typed clients for the frontend.
These aren't mutually exclusive. Many teams keep tRPC for an existing Next.js app's data layer while new services move to Encore, with the frontend calling them through Encore's generated client.
tRPC is a TypeScript library for building type-safe APIs without a schema or code generation. You define procedures on the server, and the client imports the router's type, so calls are type-checked end to end and your editor autocompletes them.
Not in the full-framework sense. tRPC is an API layer that runs inside a host server, such as Next.js, Express, Fastify, or a fetch-based runtime. It handles routing procedures and typing the client, but databases, auth, background jobs, and deployment come from elsewhere.
gRPC uses Protocol Buffers as a language-neutral contract, generates clients in many languages, and sends binary messages over HTTP/2. tRPC is TypeScript-only, needs no schema or code generation, and sends JSON over plain HTTP. gRPC suits polyglot service-to-service traffic; tRPC suits full-stack TypeScript apps.
For a TypeScript frontend and backend in the same codebase, tRPC removes a lot of glue code. REST is better when clients aren't written in TypeScript, when third parties integrate with your API, or when you want standard HTTP caching and an OpenAPI description.
It depends on what you need beyond typed calls. REST with OpenAPI or gRPC fit cross-language clients. Encore gives typed APIs with generated clients (TypeScript, Go, and OpenAPI) and also provisions the backend's infrastructure, such as databases and Pub/Sub, in your own AWS or GCP account.
Yes. A common pattern is to keep tRPC for an existing Next.js frontend's data layer while new backend services, background jobs, and infrastructure move to Encore, with the frontend calling Encore through its generated client.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.