Oct 11, 20269 min read

What Is tRPC? A Guide to tRPC, tRPC vs gRPC, and tRPC vs REST

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.

What Is tRPC?

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.

How tRPC works

  • Procedures: queries (reads), mutations (writes), and subscriptions (real-time updates over WebSockets or server-sent events).
  • Input validation: each procedure validates its input with a schema library such as Zod, and the validated type flows to both the handler and the client.
  • Transport: JSON over plain HTTP. Queries are sent as GET requests and mutations as POST, and a batching link can combine several calls into one request.
  • Adapters: tRPC runs inside a host server. Official adapters cover Next.js, Express, Fastify, standalone Node, and fetch-based runtimes.
  • Client integrations: a plain client, plus integrations with TanStack React Query for caching and loading states in React apps.

Is tRPC a Framework?

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:

  • Routing and serving HTTP: the host framework (Next.js, Express, Fastify).
  • Database access: an ORM such as Prisma or Drizzle, plus a database you provision and run.
  • Auth, background jobs, queues, cron, file storage: separate libraries and services.
  • Observability and deployment: your own tracing setup, infrastructure, and CI/CD.

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.

tRPC vs gRPC

Both are remote procedure call systems, but they target different problems.

AspecttRPCgRPC
ContractTypeScript types inferred from the server codeProtocol Buffers (.proto) schema
Code generationNoneRequired, per language
LanguagesTypeScript onlyMany (Go, Java, Python, C++, TypeScript, and more)
Wire formatJSON over HTTPBinary Protobuf over HTTP/2
Browser supportNativeNeeds gRPC-Web or a proxy
StreamingSubscriptionsUnary, server, client, and bidirectional streaming
Typical useFull-stack TypeScript appsPolyglot service-to-service communication

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.

tRPC vs REST

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:

AspecttRPCREST
Type safetyEnd to end, automatic in TypeScriptVia OpenAPI and generated clients, or by hand
ClientsTypeScript clients that can import the server's typeAny language or tool, including curl
API descriptionNone built in (community OpenAPI adapters exist)OpenAPI is the common standard
HTTP cachingLimited; queries are GETs but URLs are procedure-shapedNative, per resource
Public and partner APIsPoor fitStandard choice
Boilerplate in a TS monorepoVery lowHigher without generated clients

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.

Where tRPC Falls Short

tRPC's design choices have costs that show up as a system grows:

  • TypeScript-only clients. A mobile app, a Python service, or a partner integration can't use the router type, so you end up adding REST or OpenAPI alongside it.
  • Tight coupling. The client imports the server's type, so frontend and backend effectively share a release and a repository.
  • No infrastructure. tRPC doesn't provision databases, queues, or cron jobs, and doesn't connect them to your code.
  • Service-to-service calls. It's designed for frontend-to-backend calls. Calls between backend services need their own approach.
  • Observability. Tracing a request through procedures, database queries, and other services is something you assemble yourself.

A tRPC Alternative: Encore

Encore

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:

  • Infrastructure from code: databases, Pub/Sub, cron jobs, object storage, and secrets are declared next to the code that uses them, run locally under encore run, and are provisioned automatically in your own AWS or GCP account.
  • Type-safe service-to-service calls: calling another service is a typed function call, which tRPC doesn't cover.
  • Built-in observability: distributed tracing, metrics, and logs across services without setting up OpenTelemetry.
  • Streaming: streaming APIs for real-time use cases, comparable to tRPC subscriptions.
  • Performance: a Rust-based runtime that handled 9x the throughput of Express.js in our benchmarks.

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.

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

How to Choose

Start at the top and follow the first question you can answer with a yes:

Question 1

Do you want typed APIs plus infrastructure provisioned from your code, without building a DevOps team?

Yes →
Typed APIs and generated clients, with infrastructure in your own AWS or GCP account
Question 2

Are you building several services that share databases, Pub/Sub, or cron jobs?

Yes →
Type-safe service-to-service calls and built-in tracing
Question 3

Do services in several languages need a strict contract and binary transport, or bidirectional streaming?

Yes →
gRPCProtobuf contracts with generated clients per language
Question 4

Will mobile apps, other languages, or third parties call your API?

Yes →
RESTAny client, with OpenAPI and HTTP caching
or
Generated TypeScript and Go clients, plus an OpenAPI spec
Question 5

Is it a full-stack TypeScript app with the frontend and backend in one codebase, such as Next.js?

Yes →
End-to-end types with no schema or codegen

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.

Frequently asked questions

What is tRPC?

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.

Is tRPC a framework?

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.

What is the difference between tRPC and gRPC?

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.

Is tRPC better than REST?

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.

What is a good tRPC alternative?

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.

Can I use tRPC and Encore together?

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.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders