📑 Содержание
- 1. Что такое tRPC
- 2. Как работает end-to-end type safety
- 3. Установка и настройка
- 4. Роутеры и процедуры
- 5. Валидация с Zod
- 6. Context и Middleware
- 7. Клиент: React Query + tRPC
- 8. Обработка ошибок
- 9. tRPC + Next.js (App Router)
- 10. tRPC vs REST vs GraphQL
- 11. Паттерны и best practices
- 12. Когда НЕ использовать tRPC
- FAQ
1. Что такое tRPC
tRPC (TypeScript Remote Procedure Call) — это библиотека, которая позволяет создавать API как обычные TypeScript-функции. Клиент вызывает серверные процедуры напрямую, без ручного описания эндпоинтов, без HTTP-клиентов и без генерации типов из схем.
Ключевая идея
tRPC TypeScriptВ классическом REST API клиент и сервер — отдельные миры. Вы описываете эндпоинты на сервере, потом вручную повторяете типы на клиенте (или генерируете их из Swagger/OpenAPI). tRPC убирает этот разрыв:
- Сервер — TypeScript-функции с типизированными входом/выходом
- Клиент — вызывает эти функции как локальные, получая автокомплит и проверку типов
- Связь — TypeScript inference, без кодогенерации
TypeScript функция
автоматически
автокомплит + проверка
Что это значит на практике
Допустим, у вас на сервере есть процедура, возвращающая пользователя:
// server: router.ts
export const appRouter = router({
user: router({
getById: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
const user = await db.user.findUnique({ where: { id: input.id } });
return user; // { id: string, name: string, email: string }
}),
}),
});
На клиенте вы вызываете её так:
// client: component.tsx
const { data } = trpc.user.getById.useQuery({ id: "123" });
// data автоматически типизирован как:
// { id: string, name: string, email: string } | undefined
// IDE даёт полный автокомплит: data.name, data.email ✓
// data.phone → TS Error: Property 'phone' does not exist ✗
2. Как работает end-to-end type safety
Магия tRPC — в TypeScript type inference. Никаких runtime-схем для передачи типов. Вот как это работает под капотом:
Цепочка вывода типов
- Сервер определяет процедуру с input (Zod) и output (return type)
- Роутер экспортирует тип:
type AppRouter = typeof appRouter - Клиент импортирует только тип:
import type { AppRouter } - TypeScript выводит типы input/output для каждой процедуры
- IDE подсказывает автокомплит, ловит ошибки при компиляции
Ключевое слово — import type. Это type-only import: он удаляется при компиляции и не создаёт реальной зависимости между клиентом и сервером. В бандл клиента серверный код не попадает.
// Это type-only import — удаляется при компиляции
import type { AppRouter } from '../server/router';
// Создаём типизированный клиент
import { createTRPCReact } from '@trpc/react-query';
export const trpc = createTRPCReact<AppRouter>();
graphql-codegen) или OpenAPI (где нужен openapi-typescript), tRPC не генерирует файлы. Типы существуют только в TypeScript-компиляторе. Изменили сервер — клиент мгновенно видит изменения.
3. Установка и настройка
Пакеты
# Серверные пакеты
npm install @trpc/server zod
# Клиентские пакеты (React)
npm install @trpc/client @trpc/react-query @tanstack/react-query
# Для Next.js
npm install @trpc/next
Инициализация сервера
// server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.context<Context>().create({
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof z.ZodError ? error.cause.flatten() : null,
},
};
},
});
export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
export const createCallerFactory = t.createCallerFactory;
Структура проекта
my-app/
├── src/
│ ├── server/
│ │ ├── trpc.ts # initTRPC, экспорт router/procedure
│ │ ├── context.ts # createContext (req, session, db)
│ │ ├── router/
│ │ │ ├── index.ts # appRouter = router({ user, post, ... })
│ │ │ ├── user.ts # userRouter
│ │ │ └── post.ts # postRouter
│ │ └── middleware/
│ │ └── auth.ts # isAuthed middleware
│ ├── client/
│ │ ├── trpc.ts # createTRPCReact<AppRouter>
│ │ └── provider.tsx # TRPCProvider + QueryClientProvider
│ └── pages/ or app/ # Next.js pages/app directory
├── package.json
└── tsconfig.json
4. Роутеры и процедуры
tRPC строится вокруг двух концепций: роутеры (группируют процедуры) и процедуры (серверные функции).
Типы процедур
| Тип | HTTP-метод | Назначение | Аналог REST |
|---|---|---|---|
query |
GET | Чтение данных | GET /users/:id |
mutation |
POST | Создание / изменение / удаление | POST, PUT, DELETE |
subscription |
WebSocket | Реал-тайм подписки | WebSocket / SSE |
Пример: CRUD-роутер для постов
// server/router/post.ts
import { z } from 'zod';
import { router, publicProcedure, protectedProcedure } from '../trpc';
export const postRouter = router({
// Query — получение списка
list: publicProcedure
.input(z.object({
limit: z.number().min(1).max(100).default(20),
cursor: z.string().nullish(),
}))
.query(async ({ input, ctx }) => {
const posts = await ctx.db.post.findMany({
take: input.limit + 1,
cursor: input.cursor ? { id: input.cursor } : undefined,
orderBy: { createdAt: 'desc' },
include: { author: { select: { name: true, image: true } } },
});
let nextCursor: string | undefined;
if (posts.length > input.limit) {
const next = posts.pop();
nextCursor = next?.id;
}
return { posts, nextCursor };
}),
// Query — получение одного поста
getById: publicProcedure
.input(z.object({ id: z.string().uuid() }))
.query(async ({ input, ctx }) => {
const post = await ctx.db.post.findUnique({
where: { id: input.id },
include: { author: true, comments: true },
});
if (!post) {
throw new TRPCError({ code: 'NOT_FOUND', message: 'Post not found' });
}
return post;
}),
// Mutation — создание поста (только авторизованные)
create: protectedProcedure
.input(z.object({
title: z.string().min(1).max(200),
content: z.string().min(1),
published: z.boolean().default(false),
}))
.mutation(async ({ input, ctx }) => {
return ctx.db.post.create({
data: { ...input, authorId: ctx.session.user.id },
});
}),
// Mutation — удаление
delete: protectedProcedure
.input(z.object({ id: z.string().uuid() }))
.mutation(async ({ input, ctx }) => {
const post = await ctx.db.post.findUnique({ where: { id: input.id } });
if (post?.authorId !== ctx.session.user.id) {
throw new TRPCError({ code: 'FORBIDDEN' });
}
return ctx.db.post.delete({ where: { id: input.id } });
}),
});
Сборка роутеров
// server/router/index.ts
import { router } from '../trpc';
import { userRouter } from './user';
import { postRouter } from './post';
import { commentRouter } from './comment';
export const appRouter = router({
user: userRouter,
post: postRouter,
comment: commentRouter,
});
// Экспорт типа — это всё, что нужно клиенту
export type AppRouter = typeof appRouter;
5. Валидация с Zod
Zod — стандарт валидации в tRPC. Он выполняет двойную роль: валидирует данные в runtime и выводит TypeScript-типы.
Zod + tRPC = полная безопасность
Zod TypeScript- Compile-time: TypeScript проверяет типы при сборке
- Runtime: Zod валидирует данные при каждом запросе
- Один источник правды — Zod-схема определяет и типы, и валидацию
Практические примеры схем
import { z } from 'zod';
// Переиспользуемые схемы
const emailSchema = z.string().email('Некорректный email');
const passwordSchema = z.string()
.min(8, 'Минимум 8 символов')
.regex(/[A-Z]/, 'Нужна заглавная буква')
.regex(/[0-9]/, 'Нужна цифра');
// Схема регистрации
const registerInput = z.object({
email: emailSchema,
password: passwordSchema,
name: z.string().min(2).max(50),
role: z.enum(['user', 'admin']).default('user'),
});
// Схема с трансформацией
const createPostInput = z.object({
title: z.string().min(1).max(200).transform(s => s.trim()),
content: z.string().min(1),
tags: z.array(z.string()).max(10).default([]),
publishAt: z.string().datetime().optional(),
});
// Пагинация (переиспользуемая)
const paginationInput = z.object({
page: z.number().int().positive().default(1),
perPage: z.number().int().min(1).max(100).default(20),
sortBy: z.string().optional(),
sortOrder: z.enum(['asc', 'desc']).default('desc'),
});
// Использование в процедуре
const userRouter = router({
register: publicProcedure
.input(registerInput)
.mutation(async ({ input }) => {
// input уже типизирован И провалидирован
// input.email — string (валидный email)
// input.role — 'user' | 'admin'
}),
list: protectedProcedure
.input(paginationInput)
.query(async ({ input }) => {
// input.page — number, input.perPage — number
}),
});
.output() для валидации ответа. Это полезно, чтобы не утечь лишним данным (например, password hash):
const userPublicSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string(),
createdAt: z.date(),
});
getById: publicProcedure
.input(z.object({ id: z.string() }))
.output(userPublicSchema) // strip лишних полей
.query(async ({ input, ctx }) => {
return ctx.db.user.findUniqueOrThrow({ where: { id: input.id } });
}),
6. Context и Middleware
Context — данные запроса
Context создаётся для каждого запроса и содержит общие зависимости: подключение к БД, сессию пользователя, логгер.
// server/context.ts
import { type inferAsyncReturnType } from '@trpc/server';
import { type FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';
import { getServerSession } from 'next-auth';
import { prisma } from './db';
export async function createContext(opts: FetchCreateContextFnOptions) {
const session = await getServerSession();
return {
db: prisma,
session,
headers: opts.req.headers,
};
}
export type Context = inferAsyncReturnType<typeof createContext>;
Middleware — переиспользуемая логика
// server/middleware/auth.ts
import { TRPCError } from '@trpc/server';
import { t } from '../trpc';
export const isAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
ctx: {
session: ctx.session, // теперь session гарантированно не null
},
});
});
// Middleware для логирования
const logger = t.middleware(async ({ path, type, next }) => {
const start = Date.now();
const result = await next();
const duration = Date.now() - start;
console.log(`${type} ${path} — ${duration}ms`);
return result;
});
// Middleware для rate limiting
const rateLimit = t.middleware(async ({ ctx, next }) => {
const ip = ctx.headers.get('x-forwarded-for') ?? 'unknown';
const allowed = await checkRateLimit(ip);
if (!allowed) {
throw new TRPCError({
code: 'TOO_MANY_REQUESTS',
message: 'Rate limit exceeded',
});
}
return next();
});
// Комбинирование middleware
export const protectedProcedure = t.procedure
.use(logger)
.use(rateLimit)
.use(isAuthed);
isAuthed TypeScript знает, что ctx.session — не null. Это избавляет от проверок if (!session) в каждой процедуре.
7. Клиент: React Query + tRPC
tRPC использует TanStack Query (React Query) как транспортный слой на клиенте. Вы получаете кеширование, рефетч, оптимистичные обновления — всё из коробки.
Настройка провайдера
// client/provider.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink } from '@trpc/client';
import { useState } from 'react';
import { trpc } from './trpc';
export function TRPCProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 минут
retry: 1,
},
},
}));
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: '/api/trpc',
headers() {
return {
'x-trpc-source': 'react',
};
},
}),
],
}),
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</trpc.Provider>
);
}
Использование в компонентах
// components/PostList.tsx
import { trpc } from '@/client/trpc';
export function PostList() {
// Query — автокомплит по пути: trpc.post.list
const { data, isLoading, fetchNextPage, hasNextPage } =
trpc.post.list.useInfiniteQuery(
{ limit: 20 },
{ getNextPageParam: (lastPage) => lastPage.nextCursor },
);
// Mutation с оптимистичным обновлением
const utils = trpc.useUtils();
const deleteMutation = trpc.post.delete.useMutation({
onMutate: async ({ id }) => {
await utils.post.list.cancel();
const prev = utils.post.list.getInfiniteData({ limit: 20 });
utils.post.list.setInfiniteData({ limit: 20 }, (old) => {
if (!old) return old;
return {
...old,
pages: old.pages.map((page) => ({
...page,
posts: page.posts.filter((p) => p.id !== id),
})),
};
});
return { prev };
},
onError: (_err, _vars, context) => {
utils.post.list.setInfiniteData({ limit: 20 }, context?.prev);
},
onSettled: () => {
utils.post.list.invalidate();
},
});
if (isLoading) return <div>Загрузка...</div>;
return (
<div>
{data?.pages.map((page) =>
page.posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.author.name}</p>
<button onClick={() => deleteMutation.mutate({ id: post.id })}>
Удалить
</button>
</article>
)),
)}
{hasNextPage && (
<button onClick={() => fetchNextPage()}>Загрузить ещё</button>
)}
</div>
);
}
Prefetch и SSR
// Prefetch на сервере (Next.js App Router)
import { createServerSideHelpers } from '@trpc/react-query/server';
export default async function PostPage({ params }: { params: { id: string } }) {
const helpers = createServerSideHelpers({
router: appRouter,
ctx: await createContext(),
});
// Данные загружаются на сервере
await helpers.post.getById.prefetch({ id: params.id });
return (
<HydrationBoundary state={helpers.dehydrate()}>
<PostContent id={params.id} />
</HydrationBoundary>
);
}
8. Обработка ошибок
tRPC предоставляет типизированные ошибки, которые маппятся на HTTP-статусы:
| tRPC Code | HTTP Status | Когда использовать |
|---|---|---|
BAD_REQUEST | 400 | Невалидные данные (Zod ошибки) |
UNAUTHORIZED | 401 | Нет аутентификации |
FORBIDDEN | 403 | Нет прав доступа |
NOT_FOUND | 404 | Ресурс не найден |
CONFLICT | 409 | Конфликт (дубликат) |
TOO_MANY_REQUESTS | 429 | Превышен rate limit |
INTERNAL_SERVER_ERROR | 500 | Серверная ошибка |
Бросание ошибок на сервере
import { TRPCError } from '@trpc/server';
create: protectedProcedure
.input(createUserInput)
.mutation(async ({ input, ctx }) => {
const existing = await ctx.db.user.findUnique({
where: { email: input.email },
});
if (existing) {
throw new TRPCError({
code: 'CONFLICT',
message: 'Пользователь с таким email уже существует',
cause: { field: 'email' },
});
}
return ctx.db.user.create({ data: input });
}),
Обработка ошибок на клиенте
import { TRPCClientError } from '@trpc/client';
const mutation = trpc.user.create.useMutation({
onError: (error) => {
if (error instanceof TRPCClientError) {
// Типизированная ошибка
if (error.data?.code === 'CONFLICT') {
toast.error('Email уже занят');
return;
}
// Zod-ошибки валидации
if (error.data?.zodError) {
const fieldErrors = error.data.zodError.fieldErrors;
// fieldErrors.email → ['Некорректный email']
setErrors(fieldErrors);
return;
}
}
toast.error('Что-то пошло не так');
},
});
errorFormatter при создании tRPC, чтобы Zod-ошибки автоматически передавались клиенту в удобном формате. Подробнее о дебаге API-ошибок — в нашей статье «Чеклист отладки API-ошибок».
9. tRPC + Next.js (App Router)
tRPC и Next.js — каноническая пара. В Next.js 14+ (App Router) tRPC можно использовать как в Server Components, так и в Client Components.
API Route Handler
// app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '@/server/router';
import { createContext } from '@/server/context';
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => createContext({ req }),
});
export { handler as GET, handler as POST };
Server Components (RSC) — без HTTP
// app/posts/page.tsx (Server Component)
import { createCaller } from '@/server/router';
import { createContext } from '@/server/context';
export default async function PostsPage() {
const ctx = await createContext();
const caller = createCaller(ctx);
// Прямой вызов — без HTTP, без сериализации
const posts = await caller.post.list({ limit: 10 });
return (
<div>
{posts.posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</div>
);
}
T3 Stack
Next.js tRPC Prisma NextAuthT3 Stack — самый популярный full-stack TypeScript стартер. Включает:
- Next.js — фреймворк
- tRPC — type-safe API
- Prisma — ORM (типобезопасные запросы к БД)
- NextAuth.js — аутентификация
- Tailwind CSS — стили
npm create t3-app@latest
10. tRPC vs REST vs GraphQL
REST
Стандарт индустрии
GraphQL
Гибкие запросы
tRPC
Type-safe из коробки
| Критерий | REST | GraphQL | tRPC |
|---|---|---|---|
| Типобезопасность | ❌ Ручная / codegen | ⚠️ Codegen (graphql-codegen) | ✅ Из коробки (inference) |
| Кодогенерация | Нет / openapi-ts | Обязательна | Не нужна |
| Языки клиентов | Любые | Любые | Только TypeScript |
| Кривая обучения | Низкая | Высокая | Низкая (знание TS) |
| Over-fetching | Часто | Решено | Зависит от процедур |
| Caching | HTTP-кеш | Сложный | React Query |
| Документация | Swagger / OpenAPI | Introspection | TypeScript = документация |
| Публичное API | ✅ Стандарт | ✅ Подходит | ❌ Не подходит |
| Batching | Нет (по умолчанию) | Да | Да (httpBatchLink) |
REST — публичное API, мультиязычные клиенты, простые CRUD.
GraphQL — сложные запросы, мобильные + веб + desktop клиенты на разных языках.
tRPC — full-stack TypeScript (Next.js, T3), внутренние сервисы, стартапы.
11. Паттерны и best practices
1. Batching запросов
tRPC по умолчанию батчит запросы через httpBatchLink. Несколько вызовов, сделанных в одном рендере, объединяются в один HTTP-запрос:
// Эти 3 вызова → 1 HTTP-запрос
const user = trpc.user.getById.useQuery({ id: '1' });
const posts = trpc.post.list.useQuery({ limit: 10 });
const stats = trpc.analytics.summary.useQuery();
// Batched request:
// POST /api/trpc/user.getById,post.list,analytics.summary
2. Переиспользование input-схем
// shared/schemas.ts — общие схемы
export const idInput = z.object({ id: z.string().uuid() });
export const paginatedInput = z.object({
limit: z.number().min(1).max(100).default(20),
cursor: z.string().nullish(),
});
export const searchInput = paginatedInput.extend({
query: z.string().min(1).max(200),
});
// Используем в разных роутерах
getById: publicProcedure.input(idInput).query(/* ... */),
search: publicProcedure.input(searchInput).query(/* ... */),
3. Подписки (WebSocket)
// server/router/chat.ts
import { observable } from '@trpc/server/observable';
export const chatRouter = router({
onMessage: publicProcedure
.input(z.object({ roomId: z.string() }))
.subscription(({ input }) => {
return observable<{ text: string; author: string }>((emit) => {
const handler = (msg: Message) => {
if (msg.roomId === input.roomId) {
emit.next({ text: msg.text, author: msg.author });
}
};
eventEmitter.on('message', handler);
return () => eventEmitter.off('message', handler);
});
}),
});
// client
const { data } = trpc.chat.onMessage.useSubscription(
{ roomId: '123' },
{
onData: (message) => {
// message типизирован: { text: string, author: string }
addMessage(message);
},
},
);
4. trpc-openapi — REST из tRPC
Если нужно предоставить REST API наряду с tRPC (для внешних клиентов), используйте trpc-openapi:
import { generateOpenApiDocument } from 'trpc-openapi';
// Помечаем процедуры для REST
getById: publicProcedure
.meta({ openapi: { method: 'GET', path: '/users/{id}' } })
.input(z.object({ id: z.string() }))
.output(userSchema)
.query(/* ... */),
// Генерация OpenAPI-схемы
const openApiDoc = generateOpenApiDocument(appRouter, {
title: 'My API',
version: '1.0.0',
baseUrl: 'https://api.example.com',
});
5. Тестирование
// __tests__/post.test.ts
import { createCaller } from '@/server/router';
describe('post router', () => {
it('creates a post', async () => {
const caller = createCaller({
db: prismaMock,
session: { user: { id: 'user-1' } },
});
const post = await caller.post.create({
title: 'Test Post',
content: 'Content here',
});
expect(post.title).toBe('Test Post');
expect(post.authorId).toBe('user-1');
});
it('throws UNAUTHORIZED for unauthenticated user', async () => {
const caller = createCaller({ db: prismaMock, session: null });
await expect(
caller.post.create({ title: 'Test', content: 'Content' }),
).rejects.toThrow('UNAUTHORIZED');
});
it('validates input with Zod', async () => {
const caller = createCaller({
db: prismaMock,
session: { user: { id: 'user-1' } },
});
await expect(
caller.post.create({ title: '', content: 'Content' }),
).rejects.toThrow(); // Zod: "String must contain at least 1 character"
});
});
12. Когда НЕ использовать tRPC
- Мультиязычные клиенты — Swift (iOS), Kotlin (Android), Go — не могут импортировать TypeScript-типы
- Публичное API — внешние разработчики ожидают REST/GraphQL с документацией, а не TypeScript-монорепо
- Микросервисы на разных языках — если бэкенд на Go/Python/Java, tRPC бесполезен
- Команда не использует TypeScript — вся ценность tRPC в TS type system
✅ Используйте tRPC, если
- Full-stack TypeScript (Next.js, T3)
- Монорепо (клиент + сервер)
- Нужен максимальный DX
- Стартап / внутренний продукт
- React / React Native фронтенд
- Нужна скорость разработки
❌ Используйте REST/GraphQL, если
- Публичное API для сторонних разработчиков
- Клиенты на разных языках
- Бэкенд не на TypeScript
- Нужна Swagger/OpenAPI документация
- Микросервисная архитектура (разные языки)
- Нужна обратная совместимость
FAQ
Что такое tRPC и зачем он нужен?
tRPC — библиотека для создания type-safe API на TypeScript без кодогенерации. Обеспечивает end-to-end типобезопасность: типы с сервера автоматически доступны на клиенте через TypeScript inference. Идеален для full-stack TypeScript проектов (Next.js, T3 Stack).
Можно ли использовать tRPC с мобильными приложениями?
Да — с React Native через @trpc/react-query. Для нативных приложений (Swift, Kotlin) tRPC не подходит напрямую. Используйте trpc-openapi для генерации REST-эндпоинтов из tRPC-роутеров, или выберите REST API / GraphQL.
Чем tRPC отличается от GraphQL?
GraphQL требует SDL-схемы и кодогенерации, работает с любыми языками. tRPC использует TypeScript inference — типы выводятся автоматически, без генерации файлов. tRPC проще для TypeScript-монорепо. GraphQL лучше для мультиязычных проектов и публичных API.
Подходит ли tRPC для production?
Да. tRPC используется в production многими компаниями. Библиотека стабильна (v11), имеет активное сообщество. tRPC — основа T3 Stack. Cal.com, Ping.gg и другие крупные проекты работают на tRPC.
Как tRPC работает с идемпотентностью?
tRPC-мутации — обычные POST-запросы. Для идемпотентности добавьте middleware с Idempotency-Key заголовком, как и в REST API. Или реализуйте через Zod-схему с idempotencyKey полем.
Можно ли добавить tRPC к существующему REST API?
Да. tRPC может работать параллельно с REST: разные route handlers обрабатывают разные пути. Миграция может быть постепенной — новые эндпоинты на tRPC, старые остаются на REST.
Полезные ссылки
- Что такое REST API — основы REST для сравнения с tRPC
- Swagger и OpenAPI — документирование API (альтернатива для публичных API)
- HTTP-методы — GET, POST, PUT, DELETE (как query/mutation маппятся на HTTP)
- Аутентификация API — JWT, OAuth 2.0 (используется в tRPC middleware)
- Чеклист отладки API — дебаг ошибок (работает и для tRPC)
- Инструменты тестирования API — тестирование tRPC-процедур
🚀 Прототипируйте API быстрее
LightBox API — создавайте моковые эндпоинты для тестирования фронтенда, пока бэкенд на tRPC ещё в разработке.
- ✓ Мгновенное создание mock-API
- ✓ Настраиваемые HTTP-статусы и задержки
- ✓ JSON-ответы любой структуры
- ✓ Логирование всех запросов
- ✓ Бесплатный план
Статья опубликована: 23 февраля 2026
Автор: LightBox API Team