Skip to main content
tRPC

tRPC

Active

tRPC (TypeScript Remote Procedure Call) is a TypeScript-first framework for building end-to-end typesafe APIs without code generation or schema files. The API router is defined in TypeScript on the server; the client imports the router type and gets full TypeScript inference for all procedures, inputs, and outputs. tRPC v11 is the current version, heavily used in the Next.js, Remix, and React ecosystem via the T3 Stack.

tRPCTypeScriptRPCEnd-to-EndNext.jsT3 Stack2020

In one line

tRPC provides end-to-end TypeScript type safety from server to client with zero code generation. You define procedures (queries and mutations) on the server using TypeScript. The client imports only the type (not the runtime code) and TypeScript infers all inputs, outputs, and errors automatically. tRPC v11 uses HTTP for queries/mutations and WebSocket for subscriptions. Validation via Zod, Yup, or Superstruct. Dominant in Next.js fullstack apps (T3 Stack).

Quick Reference

FieldSizeDescription
Procedure typesquery / mutation / subscriptionquery: read operation (GET semantics). mutation: write operation (POST semantics). subscription: real-time event stream (WebSocket). No verbs in procedure names – use nouns and type implicitly communicates read vs write.
RoutercreateTRPCRouter()The server-side API definition. Composes procedures into a nested structure. Routers can include sub-routers for modular organization. The AppRouter type is exported and imported by clients.
Input validationZod, Yup, SuperstructEvery procedure can define a Zod schema for its input. tRPC validates input at runtime and TypeScript infers the validated type. Invalid input returns a 400 PARSE_ERROR.
ContextcreateTRPCContext()Per-request context: database connections, authenticated user, request headers. Context is created once per request and passed to all procedures. Used for auth, tenancy, logging.
Middlewareprocedure.use(middleware)Composable middleware for authentication, authorization, rate limiting, logging. .use() runs before the procedure. t.procedure.use(isAuthed) creates a protected procedure.
HTTP transportGET and POSTQueries use HTTP GET (cacheable). Mutations use HTTP POST. Endpoint: /api/trpc/[procedure]. Batching: multiple queries in one HTTP request (automatic client-side batching).
Type safetyZero codegenClient imports RouterOutput and RouterInput types from the server router. TypeScript inference works without JSON Schema, Protobuf, or OpenAPI specs. The router definition IS the schema.
Error handlingTRPCErrorthrow new TRPCError({ code: 'NOT_FOUND', message: 'User not found' }). Standard codes: PARSE_ERROR, BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, INTERNAL_SERVER_ERROR. Client catches errors with TRPCClientError.

Key Characteristics

Zero code generation

Unlike gRPC (protoc) or GraphQL (codegen CLI), tRPC requires no schema file or code generation step. The TypeScript server definition is the contract. Import the AppRouter type on the client and TypeScript does the rest.

Full TypeScript inference

Rename a field in a procedure output and every client call site shows a type error immediately. Add a required input field and every call site that doesn't provide it turns red. The type system catches API contract violations at development time.

TypeScript only

tRPC requires TypeScript on both client and server. Non-TypeScript clients (mobile native, third-party integrations) cannot benefit from type safety. For multi-consumer APIs serving external clients, REST or GraphQL with an OpenAPI/schema provides better interoperability.

T3 Stack standard

tRPC is the API layer in the T3 Stack (create-t3-app: Next.js + TypeScript + Tailwind + Prisma + tRPC + NextAuth). The most popular Next.js fullstack starter by install count uses tRPC as its API mechanism.

Message Format

Request
http
// Server: define procedures
import { z } from "zod";
import { createTRPCRouter, publicProcedure, protectedProcedure } from "@/server/trpc";

export const userRouter = createTRPCRouter({
  // Query: GET /api/trpc/user.byId
  byId: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      return ctx.db.user.findUnique({ where: { id: input.id } });
    }),

  // Mutation: POST /api/trpc/user.create
  create: protectedProcedure
    .input(z.object({
      name: z.string().min(1),
      email: z.string().email(),
    }))
    .mutation(async ({ ctx, input }) => {
      return ctx.db.user.create({ data: input });
    }),

  // Subscription: WebSocket /api/trpc/user.onUpdate
  onUpdate: publicProcedure
    .input(z.object({ userId: z.string() }))
    .subscription(({ input }) => {
      return observable<User>((emit) => {
        const unsub = userEventEmitter.on(input.userId, (user) => emit.next(user));
        return () => unsub();
      });
    }),
});
Response
http
// Client: full TypeScript inference, no codegen
import { trpc } from "@/utils/trpc";

// Query – input and output fully typed
const { data: user } = trpc.user.byId.useQuery({ id: "usr_123" });
//                                                ^^ TypeScript knows: { id: string }
//       ^^^ TypeScript knows: User | null

// Mutation – typed inputs and outputs
const createUser = trpc.user.create.useMutation();
await createUser.mutateAsync({
  name: "Alice",   // TypeScript enforces: string, min 1
  email: "[email protected]"  // TypeScript enforces: valid email
});
// Type error if you pass wrong types or miss required fields

// Subscription
trpc.user.onUpdate.useSubscription(
  { userId: "usr_123" },
  { onData: (user) => console.log(user) }
);

// HTTP wire format (simplified)
// GET /api/trpc/user.byId?input={"json":{"id":"usr_123"}}
// POST /api/trpc/user.create
// Body: {"json":{"name":"Alice","email":"[email protected]"}}
// Response: {"result":{"data":{"json":{"id":"usr_123","name":"Alice"}}}}

Implementations

linuxsince @trpc/server, @trpc/client (npm). Adapters: Next.js, Express, Fastify, Hono, AWS Lambda, Bun.available
macossince Same as Linux. Works in any Node.js runtime.available
windowssince Same as Linux. Works in Node.js on Windows.available
iossince @trpc/client works in React Native (Expo). No native tRPC client for Swift.available
androidsince @trpc/client works in React Native (Expo). No native tRPC client for Kotlin.available