tRPCMiddleTechnical

Что такое tRPC и какую проблему он решает? Как достигается сквозная типобезопасность (end-to-end type safety)?

tRPC — это фреймворк для создания типобезопасных API на TypeScript без генерации схем или кодогенерации. Тип AppRouter экспортируется с сервера и импортируется на клиенте как import type, что даёт полный вывод типов без рантайм-оверхеда.

Что такое tRPC

tRPC (TypeScript Remote Procedure Call) — библиотека для создания API, при которой сервер и клиент написаны на TypeScript и находятся в одном monorepo. Клиент получает полную типизацию серверных процедур — входных параметров, типов ответов и ошибок — без REST-спецификаций, без OpenAPI-схем, без GraphQL SDL и без кодогенерации.

Какую проблему решает

В классическом REST API типы между клиентом и сервером синхронизируются вручную или через кодогенерацию из схем. При изменении серверного эндпоинта клиент узнаёт об этом только в рантайме. tRPC устраняет этот разрыв: если серверная процедура изменила сигнатуру, TypeScript немедленно покажет ошибку в клиентском коде при компиляции.

Как достигается end-to-end type safety

Механизм основан на трёх компонентах:

  • Сервер: роутер с типизированными процедурами через Zod-схемы
  • Тип AppRouter: экспортируется из серверного файла как TypeScript-тип
  • Клиент: параметризуется этим типом через дженерик
// server/router.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.create();

export const appRouter = t.router({
  greeting: t.procedure
    .input(z.object({ name: z.string() }))
    .query(({ input }) => {
      return { message: `Hello, ${input.name}!`, timestamp: new Date() };
    }),

  createUser: t.procedure
    .input(z.object({
      email: z.string().email(),
      role: z.enum(['admin', 'user']),
    }))
    .mutation(async ({ input }) => {
      // Сохраняем в БД...
      return { id: crypto.randomUUID(), ...input };
    }),
});

// Экспортируем только ТИП — не серверный код!
export type AppRouter = typeof appRouter;
// client/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import { httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/router'; // только тип!

export const trpc = createTRPCReact<AppRouter>();

export const trpcClient = trpc.createClient({
  links: [httpBatchLink({ url: '/api/trpc' })],
});
// components/Greeting.tsx
import { trpc } from '../client/trpc';

export function Greeting() {
  // TypeScript знает: input = { name: string }, data = { message: string, timestamp: Date }
  const { data } = trpc.greeting.useQuery({ name: 'Alice' });

  // Ошибка компиляции: 'typo' не существует в AppRouter
  // trpc.typo.useQuery();

  return <p>{data?.message}</p>;
}

Как работает TypeScript без рантайм-оверхеда

Ключевой момент: import type { AppRouter } — это директива TypeScript, которая полностью удаляется при компиляции. В рантайме клиент не знает ничего о серверной реализации. Типы работают только на уровне TypeScript Language Server и при сборке — никакой рефлексии, никаких дополнительных запросов.

Подводные камни

  • tRPC работает только для TypeScript monorepo: клиент и сервер должны иметь доступ к одному типу AppRouter. Для отдельных сервисов на разных языках нужен другой подход.
  • Большой AppRouter с сотнями процедур может замедлить TypeScript Language Server — разбивайте на модули через mergeRouters.
  • tRPC не заменяет REST для публичных API: сторонние клиенты (мобильные приложения, внешние сервисы) не могут использовать TypeScript-типы. Для публичного API нужен OpenAPI.
  • Zod-валидация выполняется в рантайме на каждый запрос — сложные схемы могут влиять на производительность при высокой нагрузке.
  • Ошибки типов в tRPC иногда трудно читать: дженерики глубоко вложены, сообщения об ошибках могут быть длинными. Выделяйте типы в именованные переменные для лучшей диагностики.
  • Нет встроенной поддержки версионирования API — при breaking changes все клиенты обновляются одновременно. Для постепенной миграции используйте параллельные роутеры.

Common mistakes

  • Смешивать «tRPC» с похожим механизмом без критерия выбора.
  • Игнорировать риск: неверно оценить границы применения темы «tRPC» и получить хрупкое решение.
  • Показывать только синтаксис и не объяснять поведение в runtime или сборке.

What the interviewer is testing

  • Объясняет сквозная типобезопасность между серверным router и клиентским вызовом без отдельной схемы API.
  • Показывает на примере, как работает: сервер экспортирует тип AppRouter, клиент импортирует только тип и получает inference для input, output и errors без code generation.
  • Называет production-нюанс и граничный случай для темы «tRPC».

Sources

Related topics