Сервер tRPC собирается как конструктор: initTRPC создаёт заготовку, router собирает процедуры, процедура — это цепочка шагов (проверка ввода, middleware, ваш код), а адаптер подключает всё это к HTTP. В этом уроке разберём каждую деталь на примере сервера заметок — того самого, который в конце модуля превратится в проект.

Контекст: что нужно каждой процедуре

У каждого запроса свои «вещи»: подключение к базе, сведения о том, кто вошёл, заголовки. Всё это собирается в контекст (ctx) — объект, который создаётся заново для каждого запроса и попадает в каждую процедуру. Аналогия: бейдж посетителя в офисе. У каждого посетителя (запроса) он свой, и каждый сотрудник (процедура) смотрит на него, чтобы понять, с кем имеет дело.

ts
// server/context.ts
import { db } from './db';
import { getUserId } from './auth'; // your own function: session, JWT, cookie

export async function createContext(opts: { headers: Headers }) {
  return {
    db,
    userId: await getUserId(opts.headers), // string | null
  };
}

export type Context = Awaited<ReturnType<typeof createContext>>;

Что внутри getUserId, решаете вы: сессия Auth.js, JWT из заголовка, cookie. Тип контекста TypeScript берёт из возвращаемого значения функции — писать его руками не нужно.

💡 В документации tRPC для Next.js такая функция называется createTRPCContext. Но функция с тем же именем есть и в @trpc/tanstack-react-query, поэтому в курсе свою мы называем createContext — чтобы не путаться.

initTRPC: заготовка сервера

ts
// server/trpc.ts
import { initTRPC } from '@trpc/server';
import type { Context } from './context';

const t = initTRPC.context<Context>().create();

export const router = t.router;
export const publicProcedure = t.procedure;
export const createCallerFactory = t.createCallerFactory;

initTRPC.context<Context>().create() создаёт объект t — набор строительных деталей. Мы экспортируем только нужные: router (собирает процедуры), publicProcedure (процедура, которую может вызвать кто угодно) и createCallerFactory (пригодится для вызовов на сервере и тестов). Сам t наружу не отдаём, чтобы все файлы пользовались одним и тем же набором.

Процедуры: query и mutation

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

Путь вызова: контекст, middleware, проверка ввода, резолвер, ответ; ошибки прерывают конвейер
Путь вызова: контекст, middleware, проверка ввода, резолвер, ответ; ошибки прерывают конвейер

Процедуру собирают цепочкой: начинаем с publicProcedure, добавляем шаги (.input(...), .use(...)) и заканчиваем .query(...) или .mutation(...) — резолвером, то есть вашей функцией.