Как я перестал забывать про оплаты: история создания системы контроля для фрилансеров
Как это началось
У меня основная работа и пять проектов на аутсорсе. Звучит нормально, пока не приходит конец месяца — и я час сижу и пытаюсь вспомнить, кому что сделал, кто заплатил, а кто ещё нет. Задачи в Notion, время в голове, счета в Excel, договоры в папке «разное». Каждый документ — это «найти шаблон, вспомнить реквизиты, заполнить вручную».
Я искал инструмент, который держит всё это в одном месте. Не нашёл ничего, что подходило бы: либо огромная CRM для команд, либо простой трекер без финансов, либо западные продукты где нет ни самозанятости, ни русских реквизитов.
В какой-то момент я просто решил — буду строить сам. Для себя, под свою боль. Так появился Rute.
Честно скажу: первые недели я сомневался. Параллельно с основной работой, поздно вечером, без команды — это не романтика стартапа, это просто усталость и вопрос «а зачем я вообще это делаю?». Держало одно: я строю для себя, значит точно знаю что болит. Это лучшее техническое задание из всех возможных.
Цели проекта: Зачем фрилансеру своя система контроля
Один поток без переключений:
Проект → Задача → Время → Деньги → Документ → Отчёт клиенту
Без «скопируй отсюда и вставь туда». Без страха «я что-то забыл».
Конкретно это значит: открываю утром — за 10 секунд понимаю что делать сегодня. В конце проекта — документ формируется из данных, которые уже есть в системе, а не из памяти. В конце месяца — вижу реальную ставку: сколько я реально заработал в час с учётом всего потраченного времени.
Последнее — моя любимая фича. Потому что у большинства фрилансеров фактическая ставка сильно расходится с тем, что они думают. Rute показывает честную цифру.
Выбор технологического стека: Почему React и Node.js — идеальный тандем
Расскажу не просто что использовал, а что рассматривал и почему отказался. Это интереснее списка технологий.
Backend: Node.js + Express + PostgreSQL + Prisma
Смотрел на Fastify — он быстрее, но Express проще дебажить middleware-цепочку когда что-то идёт не так. На этом масштабе разница в производительности несущественна, а отладка бывает каждый день.
PostgreSQL против MongoDB — вопрос вообще не стоял долго. Финансовые данные реляционны по природе: счёт связан с оплатами, оплаты — с транзакциями, транзакции — с проектом. Строить финансовый обзор по всем проектам на документах MongoDB — это боль. На PostgreSQL — один JOIN.
Frontend: React + TanStack Query, без Next.js
Next.js не рассматривал серьёзно: приложение за авторизацией, SSR не нужен, оверхед фреймворка и vendor lock-in не оправданы. Чистый Vite + React — быстрее в разработке, проще в деплое.
TanStack Query убрал необходимость в Redux/Zustand: большинство того, что кажется «глобальным стейтом» — это на самом деле серверные данные. Когда это признаёшь — всё становится проще.
Zod на обоих концах
Это решение, о котором я не жалею. Когда добавил runtime-валидацию Zod на все API-ответы — сразу поймал три места, где схема данных между бэком и фронтом разошлась молча. Без Zod это были бы тихие баги в production. TypeScript проверяет типы на этапе компиляции, Zod — в рантайме, когда данные реально приходят. Нужны оба.
YandexGPT вместо OpenAI
Это осознанный выбор, не вынужденный. В системе хранятся реквизиты, банковские данные, финансовые документы самозанятых. Эти данные должны оставаться в российском контуре — это требование аудитории. OpenAI физически не подходит под эту задачу, как бы хорошо он ни работал.
Глубокие технические вызовы при разработке SaaS
Если вы когда-нибудь строили что-то финансовое — вы знаете это чувство, когда задача кажется простой, а потом сидишь и смотришь в код и понимаешь что упустил целый класс ситуаций. Вот три таких момента из Rute.
Race condition в лимитах
Пользователь с Free-планом нажимает «Сгенерировать документ». Генерация — асинхронная, идёт через очередь. Если сначала потратить лимит, а генерация упадёт — пользователь потерял квоту ни за что. Если сначала генерировать, а потом тратить — два параллельных запроса обходят лимит.
Решение — атомарное резервирование: инкрементируем счётчик и проверяем лимит в одной транзакции до запуска. Если job падает — декрементируем обратно. Кажется очевидным когда видишь решение, но найти его требует времени.
CSRF в SPA
Большинство туториалов по CSRF написаны для серверного рендеринга. В SPA с JWT в cookies всё по-другому. Я реализовал double-submit cookie: сервер ставит CSRF-токен в обычную cookie (не httpOnly), фронтенд читает и добавляет в заголовок каждого мутирующего запроса. Атакующий с другого домена не может прочитать cookie — CORS не даст.
Звучит просто, но одна неверная настройка SameSite или дырка в CORS whitelist — и либо защита не работает, либо легитимные запросы отклоняются. Это тот тип задач, где нужно понимать механику, а не копировать пример.
Архитектура 19 модулей
Весь backend: router → controller → service → repository → Prisma. Для одного разработчика это выглядит избыточно. Пока модулей пять — да, избыточно. Когда их становится 19 и между ними перекрёстные зависимости (биллинг знает о лимитах, документы знают о биллинге, jobs знают о документах) — структура спасает от спагетти.
Я рад что заложил это с самого начала, а не пытался навести порядок потом.
Безопасность данных: Почему для SaaS это приоритет №1
В Rute хранятся реквизиты, ИНН, банковские данные, финансовые документы. Это не todo-приложение. Если что-то утечёт — это не просто неприятно, это удар по реальным людям. Поэтому я думал об этом с первого коммита, а не добавлял потом.
- AES-256-GCM на чувствительных полях в БД — при утечке дампа данные нечитаемы без ключа
- JWT access 15 мин + refresh 30 дней с ротацией — украденный токен становится невалидным сразу после первого использования легитимным пользователем
- Изоляция данных по userId на уровне репозитория — каждый запрос к БД содержит userId из верифицированного JWT. IDOR невозможен конструктивно
- Rate-limit на 5 уровнях: Nginx, Express global, auth endpoints, AI, resend. Каждый уровень — свой класс атак
Очередь без лишней инфраструктуры
Генерацию PDF нельзя делать синхронно — таймауты и плохой UX. Я смотрел на Bull и RabbitMQ. Отказался от обоих — не потому что они плохие, а потому что это ещё одна зависимость ради задачи, которую решает таблица jobs в PostgreSQL. Меньше движущихся частей — меньше точек отказа. Когда масштаб потребует Bull — добавлю. Пока — это лишнее.
Free / Pro
Freemium: бесплатный план даёт полный функционал с лимитами, Pro снимает ограничения и открывает редактирование шаблонов.
| Функция | Free | Pro — 790 ₽/мес |
|---|---|---|
| Генерация документов | 10 / мес | без лимита |
| Генерация отчётов | 10 / мес | без лимита |
| AI-действия | 10 / мес | 200 / мес |
| Пользовательские шаблоны | — | да |
| Редактирование шаблонов | — | да |
Что бы я сделал иначе
-
Написал бы тесты на финансовую логику раньше. Когда логика с частичными оплатами и расчётом ставок стала сложной — регрессии начали появляться при каждом изменении. Тесты на критические пути с первого дня сэкономили бы мне несколько вечеров.
-
Медленнее проектировал бы схему БД. Несколько миграций пришлось делать потому, что я не додумал модель рекуррентных задач — как хранить вхождения, как считать нагрузку недели. Час на бумаге в начале стоил бы дешевле.
-
Раньше показал бы продукт живым людям. Строя для себя — не врёшь. Но рискуешь строить только для себя. Первые реальные пользователи сразу показали: онбординг важнее чем я думал, а фичи которые казались мне ключевыми — почти не используются.
Итоги разработки: Ключевые инсайты Fullstack-разработчика
-
Инструмент выбирается под задачу, не под резюме. YandexGPT вместо OpenAI, PostgreSQL вместо MongoDB, простая очередь вместо Bull — везде конкретная причина, не мода.
-
Безопасность нельзя добавить потом. Шифрование полей в БД, изоляция данных по userId — это решения уровня проектирования схемы, не уровня «добавим перед релизом».
-
Сложность добавляется когда нужна, не заранее. Большинство архитектурных ошибок которые я видел — это преждевременная сложность. Таблица jobs в PostgreSQL работает. Когда перестанет — добавлю Bull.
-
Полная ответственность за продукт меняет мышление. Когда ты один отвечаешь за идею, схему, API, безопасность, биллинг и деплой — нет команды которая поймает твою ошибку. Это делает каждое решение конкретным.
Сейчас Rute в beta. Продукт работает, биллинг подключён, пользователи есть. И знаете что изменилось лично для меня? Конец месяца перестал быть детективом. Я открываю Rute, смотрю на финансовый обзор — и за минуту понимаю картину. Это звучит просто, но именно за этим я и начинал.
Мне не нужен единорог. Мне нужно чтобы 500 человек перестали держать свои проекты в голове и снова занимались тем, ради чего выбрали свободу. Это достаточно.
Ты один. Но ты не в хаосе.
Независимость требует системы. Я строю эту систему — и сам пользуюсь ею каждый день.
Попробуйте Rute
Если вы фрилансер или самозанятый — заходите на ruteapp.ru. Свободный план без ограничений по времени. Буду рад честной обратной связи.
Если хотите обсудить стек или архитектурные решения — пишите, всегда открыт.
Хотите обсудить проект?
Если у вас есть интересный проект или вопросы, буду рад пообщаться
Связаться со мной