Что такое tRPC и зачем он нужен?
tRPC (TypeScript Remote Procedure Call) — это современная библиотека для создания типобезопасных (type-safe) API между клиентом и сервером. Главная особенность tRPC заключается в том, что он не требует генерации кода или дублирования типов. Вы просто пишете функции на сервере, а клиент получает к ним доступ с полной поддержкой TypeScript — автодополнением, проверкой типов и IntelliSense.
В отличие от REST или GraphQL, где вам нужно вручную описывать схемы, эндпоинты и типы, tRPC автоматически выводит типы из вашего серверного кода. Это радикально упрощает разработку и уменьшает количество ошибок.
Основные преимущества tRPC:
- Полная типобезопасность — ошибки типов обнаруживаются на этапе компиляции, а не в рантайме
- Отсутствие генерации кода — не нужно запускать генераторы или синхронизировать схемы
- Минимальный бойлерплейт — меньше кода, чем в REST или GraphQL
- Автоматическая документация — типы служат документацией
- Простота — работает как обычный вызов функции
tRPC идеально подходит для full-stack приложений на TypeScript (Next.js, Remix, Express, Fastify) и для микросервисной архитектуры.
Установка tRPC
tRPC состоит из нескольких пакетов. Для базового использования вам понадобятся:
# Установка серверной и клиентской частей
npm install @trpc/server @trpc/client
# Установка React-адаптера (если используете React)
npm install @trpc/react-query @tanstack/react-query
# Установка адаптера для Next.js
npm install @trpc/next @trpc/react-query @tanstack/react-query
# Установка Zod для валидации (рекомендуется)
npm install zod
Если вы используете yarn или pnpm:
yarn add @trpc/server @trpc/client
pnpm add @trpc/server @trpc/client
Для Python-разработчиков (если вы используете Python на бэкенде): tRPC — это TypeScript-библиотека, но вы можете использовать её с Python через trpc-py (экспериментальная обёртка). Однако основная экосистема — TypeScript.
Быстрый старт с примером кода
Давайте создадим простое приложение: сервер с процедурами для работы с пользователями и клиент, который их вызывает.
1. Сервер (server.ts)
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
// Создаём экземпляр tRPC
const t = initTRPC.create();
// Определяем тип пользователя
interface User {
id: number;
name: string;
email: string;
}
// Фейковая база данных
const users: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
];
// Создаём роутер с процедурами
const appRouter = t.router({
// Процедура для получения всех пользователей
getUsers: t.procedure
.query(() => {
return users;
}),
// Процедура для получения пользователя по ID
getUserById: t.procedure
.input(z.number())
.query(({ input }) => {
return users.find(user => user.id === input) || null;
}),
// Процедура для создания пользователя
createUser: t.procedure
.input(z.object({
name: z.string().min(1),
email: z.string().email(),
}))
.mutation(({ input }) => {
const newUser: User = {
id: users.length + 1,
name: input.name,
email: input.email,
};
users.push(newUser);
return newUser;
}),
});
// Экспортируем тип роутера для клиента
export type AppRouter = typeof appRouter;
2. Клиент (client.ts)
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';
// Создаём клиент
const client = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
}),
],
});
// Используем клиент
async function main() {
// Получаем всех пользователей
const allUsers = await client.getUsers.query();
console.log('All users:', allUsers);
// Получаем пользователя по ID
const user = await client.getUserById.query(1);
console.log('User with ID 1:', user);
// Создаём нового пользователя
const newUser = await client.createUser.mutate({
name: 'Charlie',
email: 'charlie@example.com',
});
console.log('Created user:', newUser);
}
main().catch(console.error);
3. Запуск сервера
import { createHTTPServer } from '@trpc/server/adapters/standalone';
const server = createHTTPServer({
router: appRouter,
});
server.listen(3000);
console.log('Server running on http://localhost:3000');
Основные методы и функции tRPC
В tRPC есть несколько ключевых концепций и методов. Рассмотрим их подробно.
1. initTRPC.create() — Инициализация tRPC
Что делает: Создаёт экземпляр tRPC, который используется для определения процедур и middleware.
Параметры:
context— функция для создания контекста (опционально)errorFormatter— функция для форматирования ошибок (опционально)
Пример:
import { initTRPC } from '@trpc/server';
const t = initTRPC.create({
// Контекст будет доступен во всех процедурах
context: async ({ req }) => {
return {
userId: req.headers.authorization,
};
},
});
2. t.router() — Создание роутера
Что делает: Объединяет несколько процедур в один роутер. Роутеры могут быть вложенными.
Параметры: Объект, где ключи — имена процедур, значения — процедуры или подроутеры.
Пример:
const userRouter = t.router({
list: t.procedure.query(() => [{ id: 1, name: 'Alice' }]),
byId: t.procedure.input(z.number()).query(({ input }) => ({ id: input, name: 'Bob' })),
});
const appRouter = t.router({
user: userRouter, // Вложенный роутер
health: t.procedure.query(() => 'OK'),
});
3. t.procedure.query() — GET-запрос (чтение данных)
Что делает: Определяет процедуру для чтения данных (аналог HTTP GET). Не изменяет состояние.
Параметры:
input— валидатор входных данных (опционально, через.input())- Функция-обработчик, принимающая
{ input, ctx }
Пример:
const greet = t.procedure
.input(z.string())
.query(({ input }) => {
return `Hello, ${input}!`;
});
4. t.procedure.mutation() — POST/PUT/DELETE-запрос (изменение данных)
Что делает: Определяет процедуру для изменения данных (аналог HTTP POST, PUT, DELETE).
Параметры: Аналогично query(), но предназначен для модификации.
Пример:
const createUser = t.procedure
.input(z.object({ name: z.string(), email: z.string().email() }))
.mutation(({ input }) => {
// Логика создания пользователя
return { id: Date.now(), ...input };
});
5. .input() — Валидация входных данных
Что делает: Добавляет валидацию входных данных для процедуры. Обычно используется с Zod, Yup или Superstruct.
Параметры: Схема валидации (Zod, Yup и т.д.).
Пример:
import { z } from 'zod';
const updateUser = t.procedure
.input(z.object({
id: z.number(),
name: z.string().optional(),
email: z.string().email().optional(),
}))
.mutation(({ input }) => {
// input безопасно типизирован
return { success: true };
});
6. t.middleware() — Middleware (промежуточное ПО)
Что делает: Позволяет добавить логику, выполняемую перед каждой процедурой (аутентификация, логирование, валидация).
Параметры: Функция, принимающая { ctx, next } и возвращающая результат next().
Пример:
const isAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.userId) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
ctx: { user: { id: ctx.userId } }, // Расширяем контекст
});
});
const protectedProcedure = t.procedure.use(isAuthed);
const secretData = protectedProcedure.query(({ ctx }) => {
return { secret: 'This is protected', userId: ctx.user.id };
});
7. TRPCError — Обработка ошибок
Что делает: Стандартизированная ошибка tRPC с HTTP-кодами. Позволяет клиенту корректно обрабатывать ошибки.
Параметры:
code— код ошибки (BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, INTERNAL_SERVER_ERROR и др.)message— сообщение (опционально)cause— причина (опционально)
Пример:
import { TRPCError } from '@trpc/server';
const getUser = t.procedure
.input(z.number())
.query(({ input }) => {
const user = db.findUser(input);
if (!user) {
throw new TRPCError({
code: 'NOT_FOUND',
message: `User with ID ${input} not found`,
});
}
return user;
});
8. httpBatchLink — Пакетная обработка запросов
Что делает: Объединяет несколько запросов в один HTTP-запрос. Улучшает производительность.
Параметры:
url— URL сервераheaders— функция для добавления заголовков (опционально)maxURLLength— максимальная длина URL (опционально)
Пример:
import { httpBatchLink } from '@trpc/client';
const client = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
headers: () => ({
Authorization: 'Bearer ' + getToken(),
}),
}),
],
});
9. httpLink — Одиночные запросы
Что делает: Отправляет каждый запрос отдельно. Полезно для больших запросов или стриминга.
Параметры: Аналогично httpBatchLink.
Пример:
import { httpLink } from '@trpc/client';
const client = createTRPCClient<AppRouter>({
links: Также в библиотеке
GraphQL в Swift: Полное руководство по библиотеке Apollo iOS
PDF генерация в JavaScript (Node.js): библиотеки и инструменты