tRPC: типобезопасный API между фронтендом и бэкендом без REST
Продвинутое4 минРедакция Innovialabs
Коротко
tRPC позволяет TypeScript-клиенту вызывать серверные функции напрямую, выводя типы входа и выхода из кода сервера без схем и кодогенерации; он хорош, когда одна команда владеет обеими сторонами в TypeScript-монорепозитории, и не подходит для публичных и многоязычных API.
Как работает tRPC
tRPC (TypeScript Remote Procedure Call) — библиотека для API, в котором клиент вызывает серверные функции так, будто они локальные. На сервере вы описываете процедуры, объединяете их в роутер и экспортируете только тип роутера. Клиент импортирует этот тип и получает полный автокомплит и проверку типов для каждого вызова.
Нет ни OpenAPI-спецификации, ни GraphQL-схемы, ни шага кодогенерации. Типы передаются через вывод типов TypeScript: поменяли поле на сервере — и каждый клиентский вызов, который на него опирается, сразу даёт ошибку компиляции. Под капотом это обычные HTTP-запросы с JSON, поэтому tRPC работает на Node.js, edge-рантаймах и во фреймворках вроде Next.js.
Роутеры и процедуры с валидацией
Процедура бывает query (чтение данных) или mutation (изменение данных). Вход проверяется во время выполнения библиотекой схем, например Zod, и та же схема даёт TypeScript-тип.
// server/trpc.ts
import { initTRPC } from "@trpc/server";
import { z } from "zod";
import { db } from "./db"; // ваш клиент базы данных
const t = initTRPC.create();
export const appRouter = t.router({
getProduct: t.procedure
.input(z.object({ id: z.string() }))
.query(({ input }) => db.product.findUnique({ where: { id: input.id } })),
createReview: t.procedure
.input(z.object({ productId: z.string(), text: z.string().min(10) }))
.mutation(({ input }) => db.review.create({ data: input })),
});
export type AppRouter = typeof appRouter;
На клиенте создаётся типизированный клиент на основе AppRouter:
import type { AppRouter } from "../server/trpc";
// клиент, созданный с AppRouter, знает все процедуры:
const product = await trpc.getProduct.query({ id: "42" }); // типизированный результат
Основные строительные блоки на практике:
- Context — данные запроса: текущий пользователь, подключение к базе.
- Middleware — переиспользуемая логика, например
protectedProcedure, который отклоняет вызовы без авторизации. - Вложенные роутеры —
user,product,order, объединённые в один роутер приложения. - Интеграция с React Query — кеширование, состояния загрузки и инвалидация в React-приложениях.
Точная настройка клиента зависит от адаптера и мажорной версии, поэтому сверяйтесь с официальной документацией для вашего стека.
tRPC, REST и GraphQL
| tRPC | REST | GraphQL | |
|---|---|---|---|
| Контракт | Типы TypeScript, выведенные из кода | URL и HTTP-методы, опционально OpenAPI | Схема с системой типов |
| Языки клиента | На практике только TypeScript | Любые | Любые |
| Кодогенерация | Не нужна | Опционально (из OpenAPI) | Обычно используется для типизированных клиентов |
| HTTP-кеширование | Возможно для query, но менее естественно | Нативное и понятное | Сложнее, обычно на клиенте |
| Гибкость выборки данных | Фиксирована в процедуре | Фиксирована в эндпоинте | Клиент выбирает поля |
| Лучше всего подходит | Full-stack TypeScript-приложения | Публичные и интеграционные API | Много клиентов с разными потребностями в данных |
tRPC не заменяет REST или GraphQL в целом — он убирает лишний слой, когда обе стороны написаны на TypeScript одной командой.
Когда tRPC — неправильный выбор
- Публичные API для партнёров и сторонних разработчиков. Им нужны стабильные, документированные и независимые от языка контракты.
- Клиенты на других языках: нативный iOS (Swift), Android (Kotlin), сервисы на Python, 1С.
- Отдельные репозитории, где клиент не может импортировать серверные типы без публикации пакетов.
- Долгоживущие версионируемые API — tRPC поощряет свободно менять контракт, а это ломает старые версии приложений, которые ещё используются, например не обновлённые мобильные приложения.
- Сильная зависимость от CDN/HTTP-кеширования ответов API.
Частый гибрид: tRPC для собственного веб-приложения плюс небольшой REST- или webhook-слой для внешних интеграций.
Частые ошибки
- Пропускать валидацию входа, потому что «TypeScript уже проверяет». Во время выполнения типов нет; валидация защищает сервер от любого клиента.
- Возвращать сырые модели базы данных и раскрывать внутренние поля.
- Складывать все процедуры в один гигантский файл роутера.
- Отдавать клиенту серверный код вместо одного лишь типа роутера.