NestJSPostgreSQL S3-совместимый storageApple / Google OAuth OpenAPI

Backend MVP мобильной соцсети — архитектура, этапы, стоимость

Вы просили архитектуру без overengineering и разбивку на этапы. Здесь и то, и другое: схема, модель данных, эндпоинты, пять этапов с ценой и сроком — и отдельно места, где такие MVP ломаются.

Сначала про опыт — прямо, без обтекаемых формулировок. Сданной социальной сети у меня нет, и говорить обратное я не буду: вы это проверите за пять минут. Что есть по факту: сданный и оплаченный backend-проект — экосистема SSO на Laravel (29 000 грн через Сейф), сданное нативное iOS-приложение на Swift с релиз-треком App Store, и собственный рабочий стенд на NestJS с живым Swagger — JWT, Guards, DTO, TypeORM; ссылку на него открою в переписке. Клиентских проектов именно на NestJS у меня нет. PostgreSQL — отдельный рабочий стенд с row-level security.

1Архитектура — три слоя, ничего лишнего

Для MVP с таким списком функций микросервисы не нужны: один сервис NestJS, одна база, отдельное хранилище медиа. Разделение по модулям внутри — чтобы потом можно было вынести то, что реально упрётся в нагрузку, а не всё сразу.

Граница · iOS-клиент → API JWT (access + refresh), rate limiting по IP и по пользователю, валидация DTO на входе, единый формат ошибок, request-id в каждом ответе для разбора инцидентов.
Модули NestJS auth (Apple/Google) · users · follows · posts · media · feed · likes · comments · moderation (блокировки, жалобы). Каждый модуль — свой контроллер, сервис и DTO; общая логика в общих провайдерах, без «божественного» сервиса на всё.
Данные PostgreSQL (пользователи, связи, публикации, реакции, жалобы) + S3-совместимое object storage для файлов. Медиа в базе не лежит — в базе только ключ объекта и метаданные.

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

2Почему этот стек, а не Firebase

NestJS + PostgreSQL

Ваши «нестандартные бизнес-правила» — главный аргумент. Правила, которые не ложатся в стандартную схему, в реляционной базе с обычным кодом пишутся как обычный код. В managed-конструкторе они превращаются в обход платформы.

Почему не Firebase

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

Supabase — разумный компромисс

Если приоритет — скорость и экономия, это тот самый вариант: PostgreSQL остаётся настоящим, авторизация и storage уже готовы, а RLS закрывает доступ на уровне базы. Готов собрать MVP на нём — скажу честно, где он ограничит.

Что не рекомендую

Очереди, кэш, отдельный сервис ленты, GraphQL — на MVP это расходы без отдачи. Добавляется тогда, когда появятся цифры, а не заранее «на вырост».

3Модель данных и эндпоинты

users          id · apple_sub · google_sub · username(UNIQUE, citext) · display_name
               · avatar_key · bio · created_at · is_deleted
follows        follower_id · followee_id · created_at        PK(follower_id, followee_id)
posts          id · author_id · caption · media_key · media_type · width · height
               · duration_ms · created_at · is_deleted
likes          user_id · post_id · created_at                PK(user_id, post_id)
comments       id · post_id · author_id · body · created_at · is_deleted
blocks         blocker_id · blocked_id · created_at          PK(blocker_id, blocked_id)
reports        id · reporter_id · target_type · target_id · reason · created_at · status

индексы, которые решают:
  follows(followee_id, follower_id)      — кто на меня подписан
  posts(author_id, created_at DESC)      — лента и профиль
  posts(created_at DESC, id DESC)        — keyset-пагинация
  likes(post_id)                         — счётчик реакций
  users(username)                        — UNIQUE, поиск по префиксу

4Пять мест, где такие MVP ломаются

Пагинация через OFFSET

Самая частая ошибка в ленте. При новых публикациях записи сдвигаются, и пользователь видит дубли или пропуски при прокрутке. Нужен keyset по паре (created_at, id) — он же не деградирует на больших объёмах.

Уникальность username

Проверка «занят ли ник» отдельным запросом перед вставкой — гонка: двое получают один ник. Решается UNIQUE-ограничением в базе и обработкой конфликта, а не проверкой в коде. Плюс citext, иначе Anna и anna — разные люди.

Медиа через свой сервер

Если видео идёт телом запроса через API, сервер упирается в память и таймауты на первых же пользователях. Файл должен идти в storage напрямую по presigned URL, а бэкенд получать только ключ объекта.

Блокировки, не доходящие до ленты

Блокировка реализована в профиле, но забыта в ленте, комментариях и поиске. Пользователь блокирует — и продолжает видеть человека. Фильтр должен применяться во всех выдачах, и это проверяется тестом, а не на глаз.

Счётчики лайков через COUNT

COUNT на каждую карточку ленты — N+1 запросов. На MVP хватает денормализованного счётчика в posts, обновляемого в той же транзакции.

Жалобы без разбора

Эндпоинт жалоб есть, а смотреть их негде. Нужен хотя бы минимальный список для владельца — иначе модерация существует только на бумаге, и это всплывает при ревью в App Store.

5Этапы, сроки, стоимость

Каждый этап заканчивается работающей частью, которую можно потрогать из приложения, а не «процентом готовности». Оплата поэтапно через Сейф.

ЭтапЧто внутриРезультатДнейЧасовСтоимость
1. Каркас и авторизация Проект, база, миграции, Apple и Google OAuth, JWT + refresh, профиль, уникальный username приложение логинится и заводит профиль73818 000 грн
2. Соцграф Подписки, подписчики, поиск пользователей, блокировки пользователи находят и подписываются52813 000 грн
3. Публикации и медиа Presigned-загрузка в storage, фото и короткое видео, метаданные, удаление публикация из приложения работает73818 000 грн
4. Лента и реакции Хронологическая лента по подпискам, keyset-пагинация, лайки, комментарии, счётчики рабочая лента63215 000 грн
5. Модерация и сдача Жалобы и их разбор, защита API, логирование, OpenAPI, деплой, передача доступов боевой контур у вас52816 000 грн
Итого MVP, фиксированная стоимость3016480 000 грн

Этапы идут последовательно и оплачиваются по факту сдачи каждого. Остановиться можно после любого — то, что сдано, остаётся у вас в Git и работает.