Вернуться к блогу

Как я перестал забывать про оплаты: история создания системы контроля для фрилансеров

14 мин
Как я перестал забывать про оплаты: история создания системы контроля для фрилансеров

Как это началось

У меня основная работа и пять проектов на аутсорсе. Звучит нормально, пока не приходит конец месяца — и я час сижу и пытаюсь вспомнить, кому что сделал, кто заплатил, а кто ещё нет. Задачи в 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 снимает ограничения и открывает редактирование шаблонов.

ФункцияFreePro — 790 ₽/мес
Генерация документов10 / месбез лимита
Генерация отчётов10 / месбез лимита
AI-действия10 / мес200 / мес
Пользовательские шаблоныда
Редактирование шаблоновда

Что бы я сделал иначе

  1. Написал бы тесты на финансовую логику раньше. Когда логика с частичными оплатами и расчётом ставок стала сложной — регрессии начали появляться при каждом изменении. Тесты на критические пути с первого дня сэкономили бы мне несколько вечеров.

  2. Медленнее проектировал бы схему БД. Несколько миграций пришлось делать потому, что я не додумал модель рекуррентных задач — как хранить вхождения, как считать нагрузку недели. Час на бумаге в начале стоил бы дешевле.

  3. Раньше показал бы продукт живым людям. Строя для себя — не врёшь. Но рискуешь строить только для себя. Первые реальные пользователи сразу показали: онбординг важнее чем я думал, а фичи которые казались мне ключевыми — почти не используются.

Итоги разработки: Ключевые инсайты Fullstack-разработчика

  1. Инструмент выбирается под задачу, не под резюме. YandexGPT вместо OpenAI, PostgreSQL вместо MongoDB, простая очередь вместо Bull — везде конкретная причина, не мода.

  2. Безопасность нельзя добавить потом. Шифрование полей в БД, изоляция данных по userId — это решения уровня проектирования схемы, не уровня «добавим перед релизом».

  3. Сложность добавляется когда нужна, не заранее. Большинство архитектурных ошибок которые я видел — это преждевременная сложность. Таблица jobs в PostgreSQL работает. Когда перестанет — добавлю Bull.

  4. Полная ответственность за продукт меняет мышление. Когда ты один отвечаешь за идею, схему, API, безопасность, биллинг и деплой — нет команды которая поймает твою ошибку. Это делает каждое решение конкретным.


Сейчас Rute в beta. Продукт работает, биллинг подключён, пользователи есть. И знаете что изменилось лично для меня? Конец месяца перестал быть детективом. Я открываю Rute, смотрю на финансовый обзор — и за минуту понимаю картину. Это звучит просто, но именно за этим я и начинал.

Мне не нужен единорог. Мне нужно чтобы 500 человек перестали держать свои проекты в голове и снова занимались тем, ради чего выбрали свободу. Это достаточно.

Ты один. Но ты не в хаосе.

Независимость требует системы. Я строю эту систему — и сам пользуюсь ею каждый день.


Попробуйте Rute

Если вы фрилансер или самозанятый — заходите на ruteapp.ru. Свободный план без ограничений по времени. Буду рад честной обратной связи.

Если хотите обсудить стек или архитектурные решения — пишите, всегда открыт.

Хотите обсудить проект?

Если у вас есть интересный проект или вопросы, буду рад пообщаться

Связаться со мной