Розробка tRPC API для веб-додатку
tRPC (TypeScript RPC)—бібліотека для побудови end-to-end типізованих API без схем та кодогенерації. TypeScript типи автоматично передаються від серверних процедур до клієнтських викликів. Працює тільки в екосистемі TypeScript й найзручніше в monorepo або fullstack-фреймворках (Next.js, Remix, SvelteKit).
Ключова ідея
// Сервер визначає процедуру
const appRouter = router({
getUser: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input, ctx }) => {
return ctx.db.user.findUnique({ where: { id: input.id } });
}),
});
// Клієнт викликає з повною типізацією—без кодогенерації
const user = await trpc.getUser.query({ id: 'user_123' });
// TypeScript знає тип user: { id: string; name: string; ... } | null
Нема REST-ендпоїнтів, нема OpenAPI схем, нема GraphQL—просто функції з типами.
Налаштування (Next.js + tRPC v11)
// server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { ZodError } from 'zod';
const t = initTRPC.context<Context>().create({
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});
export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
if (!ctx.session?.user) throw new TRPCError({ code: 'UNAUTHORIZED' });
return next({ ctx: { ...ctx, user: ctx.session.user } });
});
Маршрутизатор та процедури
// server/routers/articles.ts
export const articlesRouter = router({
list: publicProcedure
.input(z.object({ page: z.number().default(1), limit: z.number().max(100).default(20) }))
.query(async ({ input, ctx }) => {
const [items, total] = await ctx.db.$transaction([
ctx.db.article.findMany({ skip: (input.page - 1) * input.limit, take: input.limit }),
ctx.db.article.count(),
]);
return { items, total, pages: Math.ceil(total / input.limit) };
}),
create: protectedProcedure
.input(z.object({ title: z.string().min(1).max(200), body: z.string().min(10) }))
.mutation(async ({ input, ctx }) =>
ctx.db.article.create({ data: { ...input, authorId: ctx.user.id } })
),
delete: protectedProcedure
.input(z.string())
.mutation(async ({ input: id, ctx }) => {
const article = await ctx.db.article.findUnique({ where: { id } });
if (!article) throw new TRPCError({ code: 'NOT_FOUND' });
if (article.authorId !== ctx.user.id) throw new TRPCError({ code: 'FORBIDDEN' });
return ctx.db.article.delete({ where: { id } });
}),
});
Клієнт з React Query
// Автотипізація, кешування через React Query
function ArticleList() {
const { data, isLoading } = trpc.articles.list.useQuery({ page: 1 });
const createMutation = trpc.articles.create.useMutation({
onSuccess: () => utils.articles.list.invalidate(),
});
if (isLoading) return <Spinner />;
return (
<div>
{data?.items.map(article => <ArticleCard key={article.id} {...article} />)}
<button onClick={() => createMutation.mutate({ title: '...', body: '...' })}>
{createMutation.isPending ? 'Створюється...' : 'Створити'}
</button>
</div>
);
}
Коли використовувати tRPC
Ідеально для: fullstack TypeScript monorepo, мінімального API overhead, внутрішніх microservice API, швидкої ітерації зі спільними типами.
Не підходить для: публічних API з non-TS клієнтами, потреби у строгих REST/GraphQL контрактах, команд без TypeScript експертизи.
Терміни
Базова tRPC настройка (router, процедури, client hooks): 1–2 дні. З аутентифікацією, інтеграцією БД, складною валідацією вхідних даних: 3–5 днів.







