Расскажите о случае, когда вы улучшали performance, accessibility, testing или maintainability в проекте на tRPC.
Пример из практики: добавление Zod-валидации на все процедуры сразу через middleware улучшило maintainability; подключение superjson устранило баги с Date-сериализацией; покрытие API-слоя интеграционными тестами через createCallerFactory дало уверенность при рефакторинге.
Контекст задачи
В одном из проектов tRPC-бэкенд вырос органически: около 40 процедур, разбросанных по 8 роутерам. Со временем накопились проблемы: дублирование логики валидации, медленные тесты из-за реальных HTTP-запросов, отсутствие обработки ошибок для клиента, и утечки типов Date в JSON.
Что улучшили и как
1. Maintainability: вынос общей логики в middleware
Повторяющаяся проверка аутентификации была скопирована в каждую защищённую процедуру. Вынесли её в reusable middleware через t.middleware и создали protectedProcedure:
// server/trpc/middleware.ts
import { TRPCError } from '@trpc/server';
import { t } from './init';
const isAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session?.userId) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({ ctx: { ...ctx, userId: ctx.session.userId } });
});
export const protectedProcedure = t.procedure.use(isAuthed);
После этого каждый роутер просто импортирует protectedProcedure вместо дублирования условия.
2. Testing: createCallerFactory для быстрых интеграционных тестов
Тесты через HTTP занимали 3–5 с на старт. Переписали на прямые вызовы:
// tests/user.test.ts
import { createCallerFactory } from '@trpc/server';
import { appRouter } from '../server/trpc/router';
import { createMockContext } from './helpers';
const createCaller = createCallerFactory(appRouter);
test('user.getById returns correct user', async () => {
const ctx = createMockContext({ userId: 'u1' });
const caller = createCaller(ctx);
const user = await caller.user.getById({ id: 'u1' });
expect(user.name).toBe('Alice');
});
Время прогона 40 тестов сократилось с 18 с до 1.2 с — нет реального HTTP, нет запуска сервера.
3. Корректная сериализация: подключение superjson
Клиент получал даты как строки вместо Date-объектов, что вызывало баги в компонентах с форматированием. Подключили трансформер:
// server/trpc/init.ts
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
export const t = initTRPC.context<Context>().create({
transformer: superjson,
});
// lib/trpc.ts (клиент)
import { createTRPCReact } from '@trpc/react-query';
import superjson from 'superjson';
import { httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/trpc/router';
export const trpc = createTRPCReact<AppRouter>();
export const trpcClient = trpc.createClient({
links: [
httpBatchLink({
url: '/api/trpc',
transformer: superjson,
}),
],
});
4. Accessibility ошибок: форматированный errorFormatter
Zod-ошибки валидации возвращались как непонятный JSON. Добавили errorFormatter, который возвращает поле fieldErrors для форм:
export const t = initTRPC.context<Context>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
fieldErrors:
error.cause instanceof ZodError
? error.cause.flatten().fieldErrors
: null,
},
};
},
});
Подводные камни
- Middleware применяется цепочкой — порядок вызова
.use()имеет значение: логирование должно идти до проверки авторизации, иначе неавторизованные запросы не логируются. createCallerFactoryне проходит через HTTP-слой — заголовки, cookies и rate-limiting не тестируются этим способом. Для end-to-end нужен отдельный слой тестов.- При добавлении
superjsonпосле запуска в продакшене старые клиенты (без трансформера) перестанут корректно парсить ответы — нужен координированный деплой. - Refactoring роутеров с переименованием процедур ломает клиентский код сразу — это полезно, но нужно учитывать при CI: TypeScript должен проверять весь monorepo.
- Глубокое вложение middleware увеличивает время вывода типов TypeScript — разбивайте сложные цепочки на именованные переменные.
What hurts your answer
- Выдумывать опыт или говорить слишком общими фразами
- Не объяснять свою личную роль в работе с tRPC
- Не показывать результат, метрики или извлечённые уроки
What they're listening for
- Может подготовить честный пример использования tRPC
- Показывает свою роль, решения и результат
- Умеет рефлексировать над trade-offs и уроками