Сравнение Point0 и tRPC: новые подходы к фулстек-разработке на React и Bun
В экосистеме TypeScript обеспечение сквозной типизации между сервером и клиентом (End-to-End Type Safety) стало индустриальным стандартом. Разработчики больше не хотят вручную синхронизировать типы API, писать swagger-схемы или генерировать клиенты из OpenAPI. Библиотека tRPC долгое время оставалась главным инструментом для решения этой задачи в SPA-приложениях, предоставляя разработчикам иллюзию вызова серверных функций прямо на клиенте с полной проверкой типов на этапе компиляции.
Однако с развитием мета-фреймворков и среды выполнения Bun на рынке стали появляться полноценные фулстек-решения нового поколения. Одно из них — фреймворк Point0. В отличие от tRPC, являющегося лишь библиотекой для обвязки роутинга, Point0 представляет собой комплексный фреймворк на базе Bun и React. Разберем ключевые архитектурные различия между tRPC и Point0, сопоставим их код и оценим удобство разработки (Developer Experience, DX).
1. Объявление эндпоинтов и проблема масштабирования типов
Главное отличие лежит в способе организации роутов. В tRPC вы обязаны объявить каждую процедуру в локальном роутере, а затем импортировать все роутеры в единый корневой appRouter:
// tRPC: описание роута и ручная сборка
export const ideaRouter = router({
getById: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return await prisma.idea.findUniqueOrThrow({ where: { id: input.id } });
}),
});
// Сборка в корневом файле сервера
export const appRouter = router({
idea: ideaRouter,
// При добавлении новых модулей список растет бесконечно
});
export type AppRouter = typeof appRouter;
Проблема масштабирования: По мере роста проекта тип AppRouter раздувается. Когда в системе появляется более 100 эндпоинтов, IDE (в частности, плагин TypeScript в VS Code) начинает существенно тормозить. Автодополнение кода может зависать на несколько секунд при вводе trpc.idea., так как компилятору приходится вычислять гигантские вложенные типы всего дерева роутов.
В Point0 концепция центрального роутера типов отсутствует. Каждая квери или мутация является самостоятельным импортируемым объектом:
// Point0: изолированное объявление
import { root } from './root';
export const ideaViewQuery = root.lets
.query("ideaView")
.input(z.object({ id: z.string() }))
.handler(async ({ input }) => {
return await prisma.idea.findUniqueOrThrow({ where: { id: input.id } });
});
На клиенте вы импортируете конкретный запрос напрямую: import { ideaViewQuery } from './queries'. Типы этого запроса изолированы и не влияют на производительность IDE в других частях приложения. Индексация для рантайма собирается автоматически фоновым генератором фреймворка в файле конфигурации движка:
export const engine = Engine.create({
points: [ideaViewQuery, ideaUpdateMutation],
});
2. Разделение клиентского и серверного кода: конвенции против компилятора
В tRPC безопасность исходного кода обеспечивается строгой дисциплиной каталогов. Серверный код (подключение к БД, секреты) должен физически лежать в серверных папках. Клиент импортирует только типы (import type { AppRouter }), что гарантирует отсутствие серверного кода в финальном JS-бандле браузера. Если разработчик случайно импортирует саму функцию роутера без ключевого слова type, серверные библиотеки попадут на клиент, вызвав ошибку сборки.
В Point0 за разделение отвечает умный компилятор (плагин для Bun/Vite/Babel). Вы можете писать серверный обработчик прямо в файле компонента или в общем файле API. При сборке клиентского бандла компилятор автоматически вырезает тело метода .handler(...) и все его серверные зависимости (например, импорты Prisma или AWS SDK), оставляя на клиенте только схему валидации инпута и типизированную обертку для сетевого запроса. Для сервера компилятор выполняет обратную операцию — вырезает клиентские React-хуки вроде useQuery.
3. Работа с файлами и FormData из коробки
Загрузка файлов в tRPC всегда была нетривиальной задачей. Поскольку tRPC по умолчанию сериализует данные в JSON, передача бинарных файлов требует либо ручной настройки обходных эндпоинтов, либо перекодирования файлов в Base64 (что увеличивает размер трафика на 33%), либо сложной настройки плагинов для работы с FormData.
Point0 решает эту проблему на уровне ядра фреймворка благодаря интеграции с Bun. Схема валидации Zod поддерживает валидацию файлов через z.file(). При передаче файла в мутацию фреймворк автоматически преобразует объект запроса на клиенте в FormData, отправляет его в бинарном виде, а на сервере парсит обратно в типизированный объект:
// Point0: Мутация с загрузкой файла
export const uploadAvatarMutation = root.lets
.mutation("uploadAvatar")
.input(z.object({
userId: z.string(),
avatar: z.file() // Нативная валидация файла
}))
.handler(async ({ input }) => {
const bytes = await input.avatar.arrayBuffer();
// Сохранение файла на сервере...
});
4. Конфигурация бесконечной прокрутки (Infinite Queries)
В tRPC при реализации бесконечной прокрутки (useInfiniteQuery) наложено жесткое ограничение: поле курсора в объекте входных данных обязано называться строго cursor. Если вы хотите назвать его page или offset, библиотека выдаст ошибку типов.
Point0 предоставляет гибкость, позволяя указать любое поле в качестве курсора с помощью параметра pageParamFromInput:
export const ideaListQuery = root.lets
.query("ideaList")
.input(z.object({ limit: z.number(), page: z.number() }))
.handler(async ({ input }) => {
// Получение страницы данных...
})
.infinite({
pageParamFromInput: "page", // Указываем кастомное имя поля
});
5. Возврат серверных компонентов (RSC) и островов гидратации
tRPC предназначен исключительно для передачи данных (JSON). Он не знает о структуре разметки вашего приложения.
Поскольку Point0 является полноценным React-фреймворком, он поддерживает рендеринг серверных компонентов (React Server Components, RSC) прямо в ответах API. Вы можете вызвать мутацию, которая выполнит логику на сервере и вернет готовый отрендеренный HTML-кусок серверного компонента или ленивый интерактивный остров гидратации:
// Возврат компонента в ответе мутации
.handler(async ({ input }) => {
return {
html: <ServerSidebar activeId={input.id} />
};
})
Сравнительная таблица: tRPC против Point0
| Критерий | tRPC | Point0 |
|---|---|---|
| Тип решения | Библиотека маршрутизации API | Фулстек-фреймворк на базе Bun |
| Регистрация эндпоинтов | Ручная сборка в единое дерево | Автоматическая генератором проекта |
| Влияние на IDE (VS Code) | Замедляет при росте числа роутов | Отсутствует (импорты изолированы) |
| Разделение бандла | Дисциплина импорта типов (import type) | Автоматически компилятором |
| Загрузка файлов | Требует обходных путей или Base64 | Нативная поддержка через z.file() |
| Параметры пагинации | Жестко зафиксирован ключ cursor | Настраиваемый pageParamFromInput |
| Поддержка RSC / SSR | Только данные (требует обвязки) | Нативная интеграция, рендеринг островов |
Заключение
tRPC остается надежным выбором для существующих проектов, где серверная часть уже написана на Node.js (Express, Fastify) или Next.js, и вам нужна только легкая сквозная типизация.
Однако если вы строите новое приложение с нуля, ориентируясь на современный стек (React 19+, Bun, SSR/RSC) и цените скорость работы IDE, Point0 предлагает более чистую архитектуру. За счет делегирования задач разделения кода компилятору и нативной поддержки бинарных данных, Point0 делает фулстек-разработку на TypeScript более монолитной и простой.

![Node.JS [ru]](/api/digests/it_development/daily/20260718/assets/sources/we-use-js.jpg)