Предыдущая статья: Конвертация даты по временной зоне пользователя в "NestJS", а также ввод и отображение даты в "Angular"
Я разработал небольшое фулстек-приложение в качестве примера REST + WebSockets бойлерплейта на NestJS и Angular. В приложении используется PostgreSQL для хранения данных, Redis для кэширования и Minio для работы с файлами. Изначально целевой средой для развертывания был Kubernetes, но для ускорения разработки и тестирования MVP я решил использовать бесплатные облачные сервисы: Supabase для инфраструктуры и Vercel для деплоя бэкенда и фронтенда.
1. Проблемы
Инфраструктура для разработки: Для локальной разработки необходимо поднимать
PostgreSQL,Redis,Minioи сервер авторизации (Authorizer.dev). Это занимает время и требует ресурсов.Сложность деплоя в
Kubernetes: Настройка сборкиDocker-образов, процесс билда и деплоя вKubernetesзанимает много времени, особенно еслиDocker-образы имеют большой размер.Ограниченный бюджет: Для небольших проектов или тестирования
MVPнет бюджета на выделенные серверы или полноценную инфраструктуруKubernetes.
2. Решение
Я решил добавить возможность деплоя приложения не только в Kubernetes, но и в бесплатные облачные сервисы, такие как Supabase (для баз данных, хранилища и авторизации) и Vercel (для деплоя бэкенда и фронтенда). Это позволило ускорить процесс разработки и тестирования, а также снизить затраты на инфраструктуру.
3. Исходное состояние
Бэкенд:
NestJSсREST APIиWebSocketsдля рассылки событий (например, текущее серверное время).Фронтенд:
Angularдля взаимодействия с бэкендом черезRESTиWebSockets.-
Инфраструктура:
Локально запущенные сервисы:
Authorizer.devдля авторизации,PostgreSQLдля базы данных,Redisдля кэширования,Minioдля хранения файлов.Деплой в
Kubernetesс использованиемDocker-образов для бэкенда и фронтенда.PostgreSQLи миграции запускались черезDocker Compose.E2E-тесты также запускались черезDocker Compose.
4. Текущее состояние
Бэкенд и фронтенд: Работают локально, но инфраструктура полностью перенесена в
Supabase-
Используемые облачные сервисы:
Supabase Auth: Заменен локальныйAuthorizer.dev.Supabase Database: Заменены локальныеPostgreSQLиRedis.Supabase Storage: Заменен локальныйMinio.
Деплой: Бэкенд и фронтенд деплоятся на
Vercel. Из-заserverless-архитектурыVercelWebSocketsне работают, поэтому соответствующие тесты отключены.CI/CD: Сборка и деплой происходят черезGitHub Actions, включая применение миграций и запускE2E-тестов.
5. Этапы реализации
5.1. Переход на Supabase Database
Локальная
PostgreSQLзаменена наSupabase Database.Для миграций использован
pg-flyway(мини-версияFlywayбезJava). Я не хотел отказываться от функционалаFlyway, но при этом не хотел ставитьJavaв момент деплоя приложения. В итоге я написал миниверсиюflyway—pg-flyway.В
Supabaseнельзя создавать несколько баз данных, поэтому миграции запускаются в одной базе с использованием разных таблиц для учета миграций. Для решения этой проблемы при запуске мигратора можно передать название таблицы, где будет сохраняться информация о миграциях.Один пользователь используется для всех баз данных, так как
Supabaseне позволяет создавать новых пользователей с правами на создание баз. Это ограничение потребовало пересмотра логики работы с базами данных.
5.2. Переход на Supabase вместо Redis
Redisзаменен наKeyvс поддержкойPostgreSQL. В рамках текущего проекта нет специфичных дляRedisзадач, поэтому я принял решение подменитьRedisна некую имплементацию.В процессе поиска я увидел, что
CacheModuleдляNestJSпереходит на использованиеKeyv, и я написал свою оберткуnestjs-mod/keyv, которая поддерживает какRedis, так иPostgreSQL.Это не полная замена, такая замена справедлива только в простых приложениях, где мы кэшируем часть данных.
5.3. Переход на Supabase Storage
Minioзаменен наSupabase Storage.Основные изменения касались логики формирования ссылок для загрузки файлов. В отличие от
Minio, вSupabaseбакеты и политики создаются черезGUI, что немного усложняет автоматизацию (я только такой способ смог найти).Из проблем в коде, была проблема в том, что
FilesModuleбыл жестко связан сMinio, пришлось связь разорвать и создать конфигурацию для переопределения методов с уровня приложения через интеграционную конфигурацию.
5.4. Переход на Supabase Auth
Локальный
Authorizer.devзаменен наSupabase Auth.Проблемы начались сразу, так как ранее
AuthModuleбазировал свою логику полностью на логиках и коде для работы сAuthorizer.Написан новый
NestJS-модуль для работы сSupabase Auth, совместимый с предыдущей реализацией. Этот модуль был написан не с нуля, а путем копирования кода существующегоnestjs-mod/authorizer.Сейчас этот новый модуль лежит в этом проекте, но в дальнейшем он будет перенесен в
nestjs-mod/nestjs-mod-contrib. Просто у меня сейчас по работе возникло слишком много проблем с готовыми серверами авторизации, и нужно написать свою кастомную реализацию. Когда она будет написана и протестирована на обратную совместимость сSupabaseиAuthorizer, тогда и появится реализацияSupabaseв публичномnpm-пакете.
5.5. Деплой на Vercel
Эта штука сожрала очень много времени, я не буду описывать все проблемы на пути, но их было очень много. Просто закину ссылку на примеры конфигураций, жалко, что я увидел их только недавно: примеры конфигураций
Vercel, а это мой конфиг дляvercel.json.После того как вы настроите деплой на
Vercelи у вас естьe2e-тесты, часть из них будет падать с ошибками, так какVercelподнимает приложение на каждый запрос, и если приложение не оптимизировано подserverless, как мое текущее, то тесты будут падать из-за долго отвечающего сайта. Проблемы с долгим бэком я решил, просто увеличив таймаут ожидания в тестах.
5.6. Переменные окружения
Это не такая уж и проблема, но она есть. Когда мы деплоим на собственный виртуальный сервер, то сервер — это безличная штука, которую можно снести в любой момент, и она не хранит в себе переменные окружения. Они все хранятся у нас в
CI/CD.При использовании
VercelиSupabaseу нас появляются еще два места, где можно хранить переменные окружения, и нужно как-то так спроектировать деплой и запуск, чтобы учесть различные варианты. Над эту задачку я тоже много времени потратил.
5.7. Регистрация и авторизация в облаках
Не стану описывать регистрацию в сервисах
SupabaseиVercel. Просто приложу небольшое видео о том, как создавать приложения наSupabaseи прописать переменные окружения вVercel.
6. Инструкция по запуску локального кода с внешней инфраструктурой на Supabase
6.1. Инициализация
git clone git@github.com:nestjs-mod/nestjs-mod-fullstack.git
cd nestjs-mod-fullstack
npm i
cp ./example-supabase.env ./.env
6.2. Подготовка
Создать организацию и проект на Supabase
Создать
bucketс названием "images" в хранилище (пример ссылки: https://supabase.com/dashboard/project/XXX/storage/buckets)Создайте новые "S3 Access Keys" с "Access key ID" и "Secret access key" (пример ссылки: https://supabase.com/dashboard/project/gustcjgbrmmipkizqzso/settings/storage)
-
Откройте
.envи заполните пустые значения# https://supabase.com/dashboard/project/XXX/settings/api - API Settings - Project URL - URL SUPABASE_URL=empty_value # https://supabase.com/dashboard/project/XXX/settings/database?showConnect=true - Connection String - Direct connection POSTGRES_URL=empty_value # https://supabase.com/dashboard/project/XXX/settings/api - API Settings - Project API Keys - anon public SUPABASE_ANON_KEY=empty_value # https://supabase.com/dashboard/project/gustcjgbrmmipkizqzso/settings/storage - S3 Access Keys - New access key - Access key ID SERVER_MINIO_ACCESS_KEY=empty_value # https://supabase.com/dashboard/project/gustcjgbrmmipkizqzso/settings/storage - S3 Access Keys - New access key - Secret access key SERVER_MINIO_SECRET_KEY=empty_value -
Создайте и заполните все необходимые новые ключи окружения
npx --yes tsx update-files-for-vercel.ts
6.3. Запуск
npm run pm2-supabase-full:dev:start
6.4. Откройте браузер
6.5. Тестирование
npm run pm2-supabase-full:dev:test:e2e
6.6. Остановка
npm run pm2-supabase-full:dev:stop
Заключение
Проектирование и разработка кода для этой статьи заняла у меня почти два месяца частичной занятости. Так что можно задуматься о необходимости поддержки нескольких окружений для запуска приложения.
Примеры кода не буду тут приводить, так как изменений было очень много.
Поддержка обратной совместимости решается с помощью подмены необходимой имплементацией конфигурации и разными имплементациями сервисов, как в бэкенде так и на фронтенде.
Бесплатная версия облачной инфраструктуры от Supabase ограничена по мощностям и долго отвечает, деплой в такое окружение можно использовать только во время разработки MVP версии.
Так как задеплоенный в Vercel вариант работает как serverless, то у нас нет возможности использовать некие внутренние EventEmitter-ы или RxJS Subject-ы которые мы могли емитить с помощью фоновых глобальных setInterval в коде, такие логики нужно решать иначе, используя Supabase Cron, Supabase Queues, Supabase realtime.
Внедрение поддержки нескольких вариантов деплоя и запуска приложения — это очень трудозатратная штука и лучше всегда выбирать только один путь деплоя приложения.
Планы
Так как мне по работе нужно будет написать кастомный сервис авторизации, который можно будет расширять по мере необходимости, то в следующем посте я и опишу создание базовой простой версии...
Ссылки
https://nestjs.com - официальный сайт фреймворка
https://nestjs-mod.com - официальный сайт дополнительных утилит
https://fullstack.nestjs-mod.com - сайт из поста
https://nestjs-mod-fullstack.vercel.app - сайт из поста на Vercel
https://github.com/nestjs-mod/nestjs-mod-fullstack - проект из поста
https://github.com/nestjs-mod/nestjs-mod-fullstack/actions/runs/13308995633/artifacts/2585972924 - видео с E2E-тестов фронтенда
SatCat
Гуд! А не пробовали nestjs сразу на supabase бахнуть -- там вроде edge functions позволяют такое. Или нет?
kaufmanendy Автор
Неа, так не пробовал, мне интересен был Vercel изначально и к нему я уже начал искать онлайн бд, позже может чекнут функции supabase, пока не критично