tRPC для TypeScript: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое tRPC и зачем он нужен?

tRPC (TypeScript Remote Procedure Call) — это современная библиотека для создания типобезопасных (type-safe) API между клиентом и сервером. Главная особенность tRPC заключается в том, что он не требует генерации кода или дублирования типов. Вы просто пишете функции на сервере, а клиент получает к ним доступ с полной поддержкой TypeScript — автодополнением, проверкой типов и IntelliSense.

В отличие от REST или GraphQL, где вам нужно вручную описывать схемы, эндпоинты и типы, tRPC автоматически выводит типы из вашего серверного кода. Это радикально упрощает разработку и уменьшает количество ошибок.

Основные преимущества tRPC:

  • Полная типобезопасность — ошибки типов обнаруживаются на этапе компиляции, а не в рантайме
  • Отсутствие генерации кода — не нужно запускать генераторы или синхронизировать схемы
  • Минимальный бойлерплейт — меньше кода, чем в REST или GraphQL
  • Автоматическая документация — типы служат документацией
  • Простота — работает как обычный вызов функции

tRPC идеально подходит для full-stack приложений на TypeScript (Next.js, Remix, Express, Fastify) и для микросервисной архитектуры.

Установка tRPC

tRPC состоит из нескольких пакетов. Для базового использования вам понадобятся:

# Установка серверной и клиентской частей
npm install @trpc/server @trpc/client

# Установка React-адаптера (если используете React)
npm install @trpc/react-query @tanstack/react-query

# Установка адаптера для Next.js
npm install @trpc/next @trpc/react-query @tanstack/react-query

# Установка Zod для валидации (рекомендуется)
npm install zod

Если вы используете yarn или pnpm:

yarn add @trpc/server @trpc/client
pnpm add @trpc/server @trpc/client

Для Python-разработчиков (если вы используете Python на бэкенде): tRPC — это TypeScript-библиотека, но вы можете использовать её с Python через trpc-py (экспериментальная обёртка). Однако основная экосистема — TypeScript.

Быстрый старт с примером кода

Давайте создадим простое приложение: сервер с процедурами для работы с пользователями и клиент, который их вызывает.

1. Сервер (server.ts)

import { initTRPC } from '@trpc/server';
import { z } from 'zod';

// Создаём экземпляр tRPC
const t = initTRPC.create();

// Определяем тип пользователя
interface User {
  id: number;
  name: string;
  email: string;
}

// Фейковая база данных
const users: User[] = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' },
];

// Создаём роутер с процедурами
const appRouter = t.router({
  // Процедура для получения всех пользователей
  getUsers: t.procedure
    .query(() => {
      return users;
    }),

  // Процедура для получения пользователя по ID
  getUserById: t.procedure
    .input(z.number())
    .query(({ input }) => {
      return users.find(user => user.id === input) || null;
    }),

  // Процедура для создания пользователя
  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 type AppRouter = typeof appRouter;

2. Клиент (client.ts)

import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';

// Создаём клиент
const client = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:3000/trpc',
    }),
  ],
});

// Используем клиент
async function main() {
  // Получаем всех пользователей
  const allUsers = await client.getUsers.query();
  console.log('All users:', allUsers);

  // Получаем пользователя по ID
  const user = await client.getUserById.query(1);
  console.log('User with ID 1:', user);

  // Создаём нового пользователя
  const newUser = await client.createUser.mutate({
    name: 'Charlie',
    email: 'charlie@example.com',
  });
  console.log('Created user:', newUser);
}

main().catch(console.error);

3. Запуск сервера

import { createHTTPServer } from '@trpc/server/adapters/standalone';

const server = createHTTPServer({
  router: appRouter,
});

server.listen(3000);
console.log('Server running on http://localhost:3000');

Основные методы и функции tRPC

В tRPC есть несколько ключевых концепций и методов. Рассмотрим их подробно.

1. initTRPC.create() — Инициализация tRPC

Что делает: Создаёт экземпляр tRPC, который используется для определения процедур и middleware.

Параметры:

  • context — функция для создания контекста (опционально)
  • errorFormatter — функция для форматирования ошибок (опционально)

Пример:

import { initTRPC } from '@trpc/server';

const t = initTRPC.create({
  // Контекст будет доступен во всех процедурах
  context: async ({ req }) => {
    return {
      userId: req.headers.authorization,
    };
  },
});

2. t.router() — Создание роутера

Что делает: Объединяет несколько процедур в один роутер. Роутеры могут быть вложенными.

Параметры: Объект, где ключи — имена процедур, значения — процедуры или подроутеры.

Пример:

const userRouter = t.router({
  list: t.procedure.query(() => [{ id: 1, name: 'Alice' }]),
  byId: t.procedure.input(z.number()).query(({ input }) => ({ id: input, name: 'Bob' })),
});

const appRouter = t.router({
  user: userRouter, // Вложенный роутер
  health: t.procedure.query(() => 'OK'),
});

3. t.procedure.query() — GET-запрос (чтение данных)

Что делает: Определяет процедуру для чтения данных (аналог HTTP GET). Не изменяет состояние.

Параметры:

  • input — валидатор входных данных (опционально, через .input())
  • Функция-обработчик, принимающая { input, ctx }

Пример:

const greet = t.procedure
  .input(z.string())
  .query(({ input }) => {
    return `Hello, ${input}!`;
  });

4. t.procedure.mutation() — POST/PUT/DELETE-запрос (изменение данных)

Что делает: Определяет процедуру для изменения данных (аналог HTTP POST, PUT, DELETE).

Параметры: Аналогично query(), но предназначен для модификации.

Пример:

const createUser = t.procedure
  .input(z.object({ name: z.string(), email: z.string().email() }))
  .mutation(({ input }) => {
    // Логика создания пользователя
    return { id: Date.now(), ...input };
  });

5. .input() — Валидация входных данных

Что делает: Добавляет валидацию входных данных для процедуры. Обычно используется с Zod, Yup или Superstruct.

Параметры: Схема валидации (Zod, Yup и т.д.).

Пример:

import { z } from 'zod';

const updateUser = t.procedure
  .input(z.object({
    id: z.number(),
    name: z.string().optional(),
    email: z.string().email().optional(),
  }))
  .mutation(({ input }) => {
    // input безопасно типизирован
    return { success: true };
  });

6. t.middleware() — Middleware (промежуточное ПО)

Что делает: Позволяет добавить логику, выполняемую перед каждой процедурой (аутентификация, логирование, валидация).

Параметры: Функция, принимающая { ctx, next } и возвращающая результат next().

Пример:

const isAuthed = t.middleware(({ ctx, next }) => {
  if (!ctx.userId) {
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }
  return next({
    ctx: { user: { id: ctx.userId } }, // Расширяем контекст
  });
});

const protectedProcedure = t.procedure.use(isAuthed);

const secretData = protectedProcedure.query(({ ctx }) => {
  return { secret: 'This is protected', userId: ctx.user.id };
});

7. TRPCError — Обработка ошибок

Что делает: Стандартизированная ошибка tRPC с HTTP-кодами. Позволяет клиенту корректно обрабатывать ошибки.

Параметры:

  • code — код ошибки (BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, INTERNAL_SERVER_ERROR и др.)
  • message — сообщение (опционально)
  • cause — причина (опционально)

Пример:

import { TRPCError } from '@trpc/server';

const getUser = t.procedure
  .input(z.number())
  .query(({ input }) => {
    const user = db.findUser(input);
    if (!user) {
      throw new TRPCError({
        code: 'NOT_FOUND',
        message: `User with ID ${input} not found`,
      });
    }
    return user;
  });

8. httpBatchLink — Пакетная обработка запросов

Что делает: Объединяет несколько запросов в один HTTP-запрос. Улучшает производительность.

Параметры:

  • url — URL сервера
  • headers — функция для добавления заголовков (опционально)
  • maxURLLength — максимальная длина URL (опционально)

Пример:

import { httpBatchLink } from '@trpc/client';

const client = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:3000/trpc',
      headers: () => ({
        Authorization: 'Bearer ' + getToken(),
      }),
    }),
  ],
});

9. httpLink — Одиночные запросы

Что делает: Отправляет каждый запрос отдельно. Полезно для больших запросов или стриминга.

Параметры: Аналогично httpBatchLink.

Пример:

import { httpLink } from '@trpc/client';

const client = createTRPCClient<AppRouter>({
  links:                    

Рекомендаций