Перейти к содержанию
Innovialabs

tRPC: типобезопасный API между фронтендом и бэкендом без REST

Продвинутое4 мин

Коротко

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

tRPCRESTGraphQL
КонтрактТипы 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 уже проверяет». Во время выполнения типов нет; валидация защищает сервер от любого клиента.
  • Возвращать сырые модели базы данных и раскрывать внутренние поля.
  • Складывать все процедуры в один гигантский файл роутера.
  • Отдавать клиенту серверный код вместо одного лишь типа роутера.

Частые вопросы

Строго говоря, нет, но так удобнее всего. Клиенту нужен доступ к типу роутера, а это проще, когда сервер и клиент лежат в одном репозитории или общем workspace.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.