tRPC v10 процедуры и middleware часто звучат в разговорах про современные API без REST. Эта статья разбирает, как устроены процедуры в tRPC, как middleware меняют контекст и поведение вызовов, и какие практические приёмы помогут сделать приложение понятнее и безопаснее. Я расскажу и про тонкости, и про типичные ошибки, с которыми столкнулся в реальных проектах.

Основная идея процедур в tRPC

В tRPC процедура — это единица бизнес-логики, доступная клиенту через типизированный интерфейс. Процедуры объявляются на сервере и могут быть query, mutation или subscription, в зависимости от направления данных.

Ключевое преимущество в том, что типы входных данных и ответов создаются один раз и переиспользуются на сервере и клиенте. Это уменьшает вероятность рассинхрона между фронтом и бэком и экономит время на пустых проверках.

Как выглядят разные типы процедур

В повседневной работе чаще всего используются три вида процедур: query для чтения, mutation для изменений и subscription для потоков. Каждая имеет своё семантическое назначение, и их разделение помогает поддерживать ясную архитектуру и удобный кэшинг на клиенте.

Простая структура процедуры включает определение входа, бизнес-логику и возвращаемое значение. Чаще всего валидация входных данных делается через zod или подобные библиотеки, что обеспечивает строгую типизацию и предсказуемое поведение.

  • Query — безопасные операции чтения, идемпотентные и кэшируемые.
  • Mutation — операции, изменяющие состояние, требуют аккуратного обращения с побочными эффектами.
  • Subscription — длительные соединения для обновлений в реальном времени.

Валидация и обработка ошибок

tRPC не навязывает конкретную библиотеку для валидации, но комбинация tRPC и zod распространена и удобна. Валидация .input(z.object({…})) выполняется до запуска обработчика, поэтому в процедуру попадает уже проверенная структура.

Для ошибок существует класс TRPCError, который позволяет возвращать понятные коды ошибок — BAD_REQUEST, UNAUTHORIZED, FORBIDDEN и другие. Это помогает унифицировать обработку ошибок на клиенте и избежать непредсказуемых 500-х.

Тип Когда использовать
BAD_REQUEST Некорректные входные данные
UNAUTHORIZED Пользователь не аутентифицирован
FORBIDDEN Недостаточно прав

Что такое middleware и зачем они нужны

Middleware в tRPC похожи на фильтры: они перехватывают вызовы процедур, могут изменить контекст, выполнить проверку или прекратить выполнение. Это мощный способ вынести повторяющуюся логику из процедур и держать код чистым.

Типичные сценарии использования middleware — аутентификация, проверка прав, логирование, rate limiting и подготовка контекста для доступа к ресурсам. Важно контролировать порядок middleware, потому что следующий слой видит контекст, модифицированный предыдущими.

Механика работы middleware: важные детали

В tRPC middleware получают объект с ctx и next. Чтобы передать управление дальше, вызывают next(), и при необходимости возвращают модифицированный контекст через next({ ctx: newCtx }). Именно это делает middleware способом аккуратно дополнять контекст данными.

Если middleware решает, что вызов должен быть остановлен, он может выбросить TRPCError. Это позволяет централизованно обрабатывать ошибки и уменьшает дублирование в процедурах.

Пример типичного middleware

Ниже приведён компактный пример того, как выглядит middleware для проверки аутентификации и добавления пользователя в контекст.

import { initTRPC, TRPCError } from '@trpc/server';
const t = initTRPC.context().create();

const isAuthed = t.middleware(({ ctx, next }) => {
  if (!ctx.user) throw new TRPCError({ code: 'UNAUTHORIZED' });
  return next({ ctx: { ...ctx, user: ctx.user } });
});

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

Композиция middleware и порядок вызовов

При вложении нескольких middleware важно помнить: порядок имеет значение. Первый зарегистрированный middleware выполняется первым и может предотвратить выполнение остальных. Это поведение должно быть явным при проектировании цепочек.

Частая ошибка — попытка использовать middleware, которое зависит от данных, добавленных более поздним middleware. Такие зависимости приводят к багаям, которые трудно отловить. Всегда продумывайте, какие данные требует каждый уровень.

Практические рекомендации и шаблоны

Ниже — несколько проверенных практик, которые помогут структурировать tRPC-роутер и middleware.

  • Выносите повторяющуюся логику в middleware — это уменьшает размер процедур и повышает читаемость.
  • Делайте короткие, однозадачные middleware: аутентификация отдельно, авторизация отдельно, логирование отдельно.
  • Документируйте контракт контекста: какие поля гарантированно добавляются каждым middleware.
  • Не модифицируйте существующие поля контекста непредсказуемо — добавляйте новые свойства с уникальными именами.

Ловушки и типичные ошибки

Некоторые ошибки встречаются снова и снова: попытка обработать бизнес-ошибку в middleware, перепутанные порядки использования, или чрезмерно тяжёлые операции внутри middleware, которые замедляют все вызовы.

Также стоит избегать бизнес-логики в middleware. Middleware должны подготавливать контекст и проверять условия, но решения о том, что именно делать в ответ на данные, лучше оставлять в процедуре.

Мой опыт и пара реальных примеров

В одном проекте я добавлял middleware для многократной проверки прав доступа: проверка токена, загрузка пользователя, проверка роли. Сначала эти уровни были в неправильном порядке, и тесты начали падать; пересмотр логики и явное разделение исправили ситуацию.

Другой случай — попытка реализовать кеширование в middleware. Это сработало, но я столкнулся с сложностью инвалидации. Вывод оказался прост: кеширование лучше делегировать специализированному слою, а middleware использовать только для метаданных и маршрутизации.

Небольшая контрольная схема для проектирования

Предлагаю краткий чеклист, который можно применять при создании новой процедуры или middleware. Он помогает не забыть важное и избежать классических ошибок.

  • Определите, что должно быть в ctx до и после middleware.
  • Убедитесь, что middleware короткие и не делают тяжелой работы.
  • Проверяйте порядок регистрации middleware и их зависимости.
  • Используйте TRPCError для явных ошибок и кодов ответа.

Интеграция с клиентом и типизация

Одно из преимуществ tRPC — общая типизация сервера и клиента. Клиент получает автодополнение и строгие типы для процедур, если проекты настроены правильно. Это сокращает количество багов, связанных с неверными параметрами.

При добавлении новых полей в контекст помните, что клиент напрямую не видит ctx, но серверные middleware должны оставаться совместимыми между версиями, иначе типизация перестанет отражать реальное состояние приложения.

Когда лучше не использовать middleware

Не во всех случаях middleware упрощают жизнь. Если задача касается одного специфичного эндпоинта, добавление общего middleware может усложнить код и замедлить выполнение. В таких ситуациях проще оставить логику в процедуре.

Также не стоит использовать middleware как универсальный способ для всех кросс-функциональных задач. Для сложных транзакций и бизнес-цепочек лучше выбирать отдельные сервисы или слой сервисной логики.

Короткий итог мыслей

Процедуры дают структуру для бизнес-операций, а middleware — инструмент для общей и повторяемой логики. Важно держать middleware простыми, проектировать контекст прозрачно и контролировать порядок слоёв. Это уменьшит количество ошибок и облегчит поддержку кода.

При проектировании API всегда полезно начать с простых правил: чёткое разделение ответственности и минимальные зависимости между слоями. Тогда tRPC станет инструментом, а не источником сложности.