Назад к заметкам
5 мин чтенияCase studyMigrationBackendBunElysiaSupabase

От Supabase к собственному монорепо: как я переписал EcoMap с BaaS

EcoMap быстро взлетает на Supabase, потом я переписываю всё на монорепо Bun/Elysia/Drizzle. Почему я ушёл с BaaS, битва с Telegram-авторизацией на вебе, которая стоила дороже всего, и что я бы сказал перед миграцией.

EcoMap — civic-tech продукт для Узбекистана: житель ставит на карте метку, прикладывает фото экологического нарушения и следит за ним до решения. Вход через Telegram, Telegram-бот, кнопка «Помочь решить», админка, три языка (ru/uz/en). Я сделал его в одиночку.

И сделал дважды.

Первая версия вышла на Next.js + Supabase примерно за четыре месяца. Вторая — полная переписка на собственном монорепо Bun/Elysia/Drizzle. Ниже — почему я выбросил работающий продукт, во что реально обошлась переписка, и одна задача, которая съела больше времени, чем всё остальное вместе взятое.

v1: Supabase быстро довёл до продукта

Первый заход — 51 коммит, с августа по декабрь — опирался на Supabase во всём, что нужно бэкенду в первый день: Postgres, авторизация, хранилище, row-level security, SDK и на сервере (SSR), и на клиенте. Сверху: TanStack Query, редактор Tiptap для тел отчётов, фоновая очередь с Telegram-ботом на grammY и прогон через OpenAI для категоризации входящих отчётов.

Вот честный аргумент за BaaS. У меня был реальный, мультиязычный, карто-центричный продукт с авторизацией и ботом за недели, а не за кварталы. Если ты проверяешь идею — это правильный трейд-офф.

И он же тихо копил причины, по которым я уйду.

Почему я ушёл с BaaS

Ни одна из них не про «Supabase плохой». Это форма сделки, которую ты заключаешь, когда твой бэкенд живёт внутри чужой платформы.

  • Lock-in там, где это важнее всего. Авторизация, правила хранилища и слой данных были выражены в идиомах Supabase. Чем больше рос продукт, тем больше моей логики завязывалось на конкретную платформу.
  • Швы вылезают по краям. Тяжёлым был не CRUD — а cross-cutting concerns: cross-origin сессии, фоновая работа, нативная обработка изображений. Именно там BaaS даёт меньше всего пространства.
  • Мне нужен был единый type-safe хребет. Клиент и сервер, расходящиеся и залатанные сгенерированными типами и хуками запросов, — это ок, пока не перестаёт быть ок.
  • Замена уже была. Я поддерживаю собственный boilerplate-монорепо на Bun/Elysia/Next.js (ShipKit), на котором работают несколько моих продуктов. Переписка была не риском с нуля — а переездом на стек, который я контролирую и переиспользую.

Так v2 стал осознанным уходом с платформы, а не переписыванием ради переписывания.

v2: стек, который я реально контролирую

Вторая версия — 94 коммита, февраль–март — это монорепо на Turborepo: apps/web, apps/admin, apps/api, apps/jobs плюс около десяти общих пакетов. Составные части:

  • API: Elysia.js на Bun, типизирован сквозь клиент Next.js через Eden Treaty — без codegen, без расхождений.
  • Данные: Drizzle ORM поверх чистого PostgreSQL.
  • Авторизация: Better Auth с кешем сессий в Redis.
  • Бот: grammY (+conversations) для Telegram.
  • Медиа: sharp для обработки фото нарушений.
  • Фронтенд: Next.js 16, React 19, Tailwind v4, shadcn/ui, next-intl для ru/uz/en.

Тот же продукт — на моих примитивах. Вот где стало интересно.

Что болело: Telegram-авторизация на вебе

Вход через Telegram тривиален внутри Telegram. В открытом вебе — нет, и именно здесь я потратил больше всего итераций с большим отрывом.

Бот аутентифицирует пользователя и отдаёт результат обратно на страницу через postMessage — payload { event: 'auth_result', result: {...} }. Страница должна превратить это в настоящую персистентную сессию. Каждая сложная проблема жила в этой передаче:

  • Cross-origin cookies. Сессионную cookie ставит API на origin, отличном от страницы. Чтобы она реально «прилипла», понадобились credentials: 'include' на клиенте, правильные атрибуты SameSite/secure у cookie и setSessionCookie от Better Auth на сервере — и заставить все три совпасть, прежде чем хоть что-то заработает.
  • USE_SECURE_COOKIES, а не NODE_ENV. Завязывать поведение secure-cookie на NODE_ENV — классическая ловушка: локальный HTTPS, staging и прод не раскладываются чисто на «development vs production». Я вынес это в явный флаг, чтобы безопасность cookie была осознанным конфигом, а не побочным эффектом режима сборки.
  • Дубли пользователей. Одна Telegram-личность может прийти не раз. Аккаунты, созданные ботом, я ключую по синтетическому валидируемому email (telegram_{id}@ecomap.uz), чтобы вернувшийся пользователь резолвился в один профиль, а не плодил дубликаты, и синхронизирую свежие Telegram-данные в profiles на /me.

Ничего из этого не гламурно. Но это ровно то, что модуль авторизации BaaS прячет — до того дня, когда тебе нужно, чтобы он вёл себя иначе, и тогда ты дерёшься с абстракцией, а не с задачей.

Тихие победы

Три вещи, которые были болезненны на платформе и чисты на собственном стеке:

  • Единый type-safe хребет. Eden Treaty экспортирует Elysia-API прямо в клиент. У форм запроса и ответа один источник правды; рефактор на сервере всплывает как ошибка типов в приложении, а не как рантайм-сюрприз в четырёх приложениях монорепо.
  • Нативная обработка изображений на моих условиях. sharp — нативная зависимость, её надо аккуратно собирать и деплоить: API я собираю с --external sharp и запускаю под Bun. Владение рантаймом превратило это в строку конфига, а не в ограничение платформы.
  • Фоновая работа вне HTTP-пути. Деривативы фото, уведомления и AI-категоризация — место в очереди, а не в запросе. Отдельный сервис apps/jobs плюс общий пакет джоб обрабатывают их под systemd, и веб-слой остаётся отзывчивым.

Как реально шёл рефактор

Я не набивал 94 коммита миграции руками. Переписка была plan-driven: docs/migration-plan/TASKS.md с чеклистом и таблицей прогресса, исполняемый через Claude Code — отметил задачу, обновил таблицу, дальше. Это тот же структурированный мультиагентный воркфлоу, который я гоняю по своим проектам, и во многом именно поэтому фулстек-переписка уложилась в несколько недель вечеров. (Об этом паттерне я писал отдельно.)

Если ты на BaaS и решаешь, мигрировать ли

Я не собираюсь советовать выдирать Supabase. Трейд-офф v1 был верным — он вывел реальный продукт к реальным пользователям быстро. Мигрируй тогда, и только тогда, когда платформа начинает стоить тебе на краях:

  • Ты дерёшься с абстракциями авторизации/хранилища больше, чем пользуешься ими.
  • Твоим cross-cutting concerns — сессиям, фоновым джобам, нативным зависимостям — нужен контроль, которого платформа не даёт.
  • У тебя уже есть (или ты можешь обосновать) стек, который переиспользуешь, — тогда переписка накапливается, а не остаётся разовой.

Для EcoMap все три были правдой, и миграция окупилась контролируемой type-safe кодовой базой, которую я расширяю, не спрашивая разрешения.


EcoMap живёт на ecomap.uz. Если планируешь разработку или миграцию с BaaS — это как раз та работа, которую я беру.

Работа со студией

Есть похожий проект?

Новый продукт, переработка или AI-функция — расскажите, что строите, и мы заскоупим вместе с вами и вернёмся с планом.

Ответим в течение 24 часов · ~13 лет опыта · NDA по запросу

Автор
Davron Yuldashevdavr.dev