What is tRPC and why do you need it?
tRPC (TypeScript Remote Procedure Call) is a modern library for creating type-safe APIs between client and server. The main feature of tRPC is that it does not require code generation or type duplication. You simply write functions on the server, and the client accesses them with full TypeScript support — autocompletion, type checking, and IntelliSense.
Unlike REST or GraphQL, where you need to manually describe schemas, endpoints, and types, tRPC automatically infers types from your server code. This radically simplifies development and reduces the number of errors.
Main advantages of tRPC:
- Full type safety — type errors are detected at compile time, not at runtime
- No code generation — no need to run generators or synchronize schemas
- Minimal boilerplate — less code than in REST or GraphQL
- Automatic documentation — types serve as documentation
- Simplicity — works like a regular function call
tRPC is ideal for full-stack TypeScript applications (Next.js, Remix, Express, Fastify) and for microservice architecture.
Installing tRPC
tRPC consists of several packages. For basic usage you will need:
# Installing server and client partsnpm install @trpc/server @trpc/client
# Installing React adapter (if using React)npm install @trpc/react-query @tanstack/react-query
# Installing adapter for Next.jsnpm install @trpc/next @trpc/react-query @tanstack/react-query
# Installing Zod for validation (recommended)npm install zodIf you use yarn or pnpm:
yarn add @trpc/server @trpc/clientpnpm add @trpc/server @trpc/clientFor Python developers (if you use Python on the backend): tRPC is a TypeScript library, but you can use it with Python via trpc-py (experimental wrapper). However, the main ecosystem is TypeScript.
Quick start with code example
Let's create a simple application: a server with procedures for working with users and a client that calls them.
1. Server (server.ts)
import { initTRPC } from '@trpc/server';import { z } from 'zod';
// Create a tRPC instanceconst t = initTRPC.create();
// Define the User typeinterface User { id: number; name: string; email: string;}
// Fake databaseconst users: User[] = [ { id: 1, name: 'Alice', email: 'alice@example.com' }, { id: 2, name: 'Bob', email: 'bob@example.com' },];
// Create a router with proceduresconst appRouter = t.router({ // Procedure to get all users getUsers: t.procedure .query(() => { return users; }),
// Procedure to get a user by ID getUserById: t.procedure .input(z.number()) .query(({ input }) => { return users.find(user => user.id === input) || null; }),
// Procedure to create a user createUser: t.procedure .input(z.object({ name: z.string().min(1), email: z.string().email(), })) .mutation(({ input }) => { const newUser: User = { id: users.length + 1, name: input.name, email: input.email, }; users.push(newUser); return newUser; }),});
// Export the router type for the clientexport type AppRouter = typeof appRouter;2. Client (client.ts)
import { createTRPCClient, httpBatchLink } from '@trpc/client';import type { AppRouter } from './server';
// Create the clientconst client Also in library
Task Queues in Java: Overview and Implementation with java.util.concurrent
Elasticsearch .NET for C# (.NET): Complete Guide with Examples
Working with Dates in Kotlin: Overview of Libraries and Tools