Локально NestJS работает очень удобно: npm run start:dev сам перезапускает сервер при изменении кода, а ошибки сразу видны в терминале. Но у production-среды совсем другие требования. Там приложение должно работать круглосуточно, само подниматься после перезагрузки сервера, хранить секреты отдельно от кода и общаться с внешним миром только по защищённому HTTPS. В этой статье мы пошагово выведем на VPS API небольшой службы доставки из Ташкента и разберём, на что обратить внимание на каждом этапе.
Какой сервер нужен: VPS или обычный хостинг
NestJS — это постоянно работающий процесс Node.js. В отличие от PHP-сайтов, он не запускается заново на каждый запрос, а слушает свой порт и держит фоновые задачи. Поэтому ему нужна среда, где можно запустить долгоживущий процесс. Самый надёжный вариант — VPS: вы сами выбираете версию Node.js, ставите PM2 или systemd и настраиваете Nginx как угодно. Возможность запускать Node.js на обычном shared-хостинге зависит от тарифа и настроек панели, поэтому перед стартом проекта это стоит уточнить в кабинете sayt.uz или через техподдержку.
В нашем примере API заказов работает по адресу api.example.uz. Он использует PostgreSQL, отправляет курьерам уведомления в Telegram и обменивается JSON с мобильным приложением. Сервер — VPS с Ubuntu, A-запись домена указывает на его IP-адрес.
Подготовка кода к production
Начните с файла main.ts. Приложение должно брать порт из переменной окружения, слушать только внутренний интерфейс и корректно обрабатывать сигналы остановки. Вызов enableShutdownHooks() позволяет аккуратно закрыть соединения с базой, когда PM2 или systemd останавливает процесс. А чтобы за Nginx видеть реальный IP клиента, нужно включить настройку Express trust proxy.
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.set('trust proxy', 1);
app.enableShutdownHooks();
const port = Number(process.env.PORT ?? 3000);
await app.listen(port, '127.0.0.1');
}
bootstrap();
Секреты — пароль базы, ключ JWT, токен Telegram-бота — никогда не должны лежать в коде или Git-репозитории. Стандартный путь — записать их в файл .env на сервере и читать через модуль @nestjs/config. Выполните chmod 600 .env, чтобы файл мог читать только пользователь, от имени которого работает приложение. Если файл лежит внутри веб-каталога, убедитесь, что Nginx не отдаёт его наружу.
Сборка и запуск на сервере
В production TypeScript напрямую не исполняется. Команда nest build компилирует его в обычный JavaScript в папке dist, и сервер запускает именно эти файлы. Для установки зависимостей лучше использовать npm ci вместо npm install: она ставит точные версии из package-lock.json, и приложение не сломается из-за неожиданного обновления пакета.
cd /var/www/api
git pull
npm ci
npm run build
npm prune --omit=dev
NODE_ENV=production node dist/main.js
Последняя команда нужна, чтобы проверить, что приложение вообще стартует. Если в терминале нет ошибок и curl http://127.0.0.1:3000 возвращает ответ, можно идти дальше. npm prune --omit=dev удаляет пакеты, нужные только для разработки, и экономит место на диске.
Миграции базы: откажитесь от synchronize
Опция synchronize: true в TypeORM удобна при разработке, но в production она сама меняет структуру таблиц и может привести к потере данных. На боевом сервере её нужно отключить и применять изменения через файлы миграций. В проектах на Prisma для этого есть отдельная команда: она выполняет только ещё не применённые миграции и не создаёт новых.
# Prisma
npx prisma migrate deploy
# TypeORM (если data-source собран в dist)
npx typeorm migration:run -d dist/data-source.js
Сделайте привычкой снимать резервную копию базы перед каждой миграцией. Для службы доставки потеря таблицы заказов — это не просто техническая проблема, а прямые потери денег и доверия клиентов.
Постоянная работа через PM2
Если закрыть терминал, процесс node dist/main.js остановится. Чтобы держать его в фоне, поднимать после падения и автоматически запускать при перезагрузке сервера, используем менеджер процессов PM2. Кластерный режим дополнительно запускает приложение на нескольких ядрах процессора и ускоряет ответы под нагрузкой.
npm install -g pm2
NODE_ENV=production pm2 start dist/main.js --name api -i 2
pm2 save
pm2 startup systemd
pm2 logs api --lines 50
Команда pm2 startup выведет на экран строку с sudo — её нужно скопировать и выполнить, иначе после перезагрузки приложение не поднимется. Помните, что в кластерном режиме у каждого экземпляра своя память: если приложение хранит сессии или кеш в памяти процесса, их нужно перенести в общее хранилище, например Redis.
Nginx как reverse proxy и HTTPS
Приложение слушает только 127.0.0.1:3000, а внешние запросы принимает Nginx и передаёт ему. Такая схема перекладывает на Nginx SSL, сжатие, статику и ограничения размера запросов, а NestJS занимается только бизнес-логикой. Если используются WebSocket, обязательно передавайте заголовки Upgrade.
server {
listen 80;
server_name api.example.uz;
client_max_body_size 10m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
После сохранения проверьте синтаксис командой sudo nginx -t и перезагрузите конфигурацию через sudo systemctl reload nginx. Затем получите сертификат Let's Encrypt: sudo certbot --nginx -d api.example.uz. Certbot сам добавит блок для порта 443 и настроит таймер автоматического продления.
Проверки после запуска
Когда API заработал, обязательно пройдитесь по нескольким пунктам. Этот список закрывает самые частые проблемы первой недели:
- После перезагрузки сервера
pm2 listпоказывает приложение в статусе online. - Порт 3000 закрыт снаружи: в файрволе открыты только 22, 80 и 443.
- Установлен
pm2 install pm2-logrotate, чтобы логи не заполнили диск. - Есть простой эндпоинт
/health, по которому мониторинг следит за состоянием API.
Заранее договоритесь и о процессе выкладки обновлений. Простейший вариант: git pull, npm ci, npm run build, миграции и pm2 reload api. В кластерном режиме reload заменяет экземпляры по одному, поэтому пользователи не замечают перерыва во время обновления.