Переменные окружения и конфигурация

Секреты и настройки живут в переменных окружения, а не в коде: .env для разработки, реальное окружение в проде.
«Конфиг отделяют от кода: один и тот же образ приложения работает и локально, и на проде — меняются только переменные.»

Строка подключения к базе, секрет для JWT, ключи внешних сервисов — всё это секреты, которым не место в коде и тем более в git. Стандартное решение — переменные окружения. В этом уроке разберём, как читать их в Express, как использовать файл .env в разработке и зачем нужен NODE_ENV.

process.env и .env

Node даёт доступ к переменным окружения через process.env. В разработке их удобно держать в файле .env и загружать пакетом dotenv (или встроенным флагом Node):

# .env  (НЕ коммитим в git!)
PORT=3000
DATABASE_URL=postgres://localhost/myapp
JWT_SECRET=очень-длинный-случайный-секрет
require('dotenv').config();  // загрузит .env в process.env

const PORT = process.env.PORT || 3000;
const SECRET = process.env.JWT_SECRET;

if (!SECRET) {
  throw new Error('JWT_SECRET не задан -- приложение не запустится');
}

app.listen(PORT);

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

NODE_ENV и режимы

Переменная NODE_ENV сообщает приложению, в каком режиме оно работает. В продакшене её ставят в production — Express при этом включает оптимизации и ускоряется в несколько раз:

const isProd = process.env.NODE_ENV === 'production';

// в проде -- лаконичные ошибки, в разработке -- подробные
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({
    error: err.message,
    stack: isProd ? undefined : err.stack  // стек только в dev
  });
});

Загрузчик конфига в браузере

Смоделируем чтение конфигурации с дефолтами и проверкой обязательных полей — типовой паттерн старта Express-приложения:

function loadConfig(env) {
  const required = ['JWT_SECRET', 'DATABASE_URL'];
  for (const key of required) {
    if (!env[key]) throw new Error('Не задана переменная: ' + key);
  }
  return {
    port: Number(env.PORT) || 3000,
    secret: env.JWT_SECRET,
    db: env.DATABASE_URL,
    isProd: env.NODE_ENV === 'production'
  };
}

const config = loadConfig({
  PORT: '8080',
  JWT_SECRET: 'abc',
  DATABASE_URL: 'postgres://...'
});
console.log(config.port);   // 8080
console.log(config.isProd); // false

Как работает под капотом

dotenv читает файл .env построчно и дописывает пары ключ-значение в process.env ещё до запуска остального кода — поэтому require('dotenv').config() ставят первой строкой. В продакшене файла .env часто нет вовсе: переменные задаёт система запуска (Docker, PM2, облако), а приложение читает их из того же process.env, ничего не зная об источнике.

Частые ошибки

  • Закоммитить .env. Это утечка всех секретов. Добавь его в .gitignore сразу.
  • Хардкодить секреты в коде. Их нельзя ротировать без правки исходников и они попадают в историю git.
  • Забыть NODE_ENV=production. Без него Express работает медленнее и выдаёт лишние детали ошибок.

Best practices

  • Все секреты и настройки — только в переменных окружения.
  • Держи .env.example со списком переменных без значений — для документации.
  • Проверяй обязательные переменные на старте и падай с понятной ошибкой, если их нет.

Итоги

Конфигурацию отделяют от кода: .env в разработке, реальные переменные окружения в проде, а NODE_ENV переключает режимы. Теперь приложение настраиваемо и не хранит секретов в репозитории. В финальном уроке разберём саму выкатку в продакшен.

Двенадцать факторов и конфиг

Принцип "конфигурация — в окружении" не выдумка, а один из пунктов методологии Twelve-Factor App, на которую опираются современные облачные сервисы. Идея проста: код приложения должен быть одинаков для всех окружений, а различия (адрес базы, ключи, режим) задаются снаружи через переменные. Тогда один и тот же артефакт без пересборки едет из теста в стейджинг и в прод — меняется только набор переменных. Это даёт воспроизводимость, упрощает откаты и делает невозможной случайную утечку прод-секрета в репозиторий. Файл .env.example с перечнем нужных переменных без значений служит живой документацией контракта между кодом и окружением.

Проверьте себя
1. Почему .env нельзя коммитить в git?
AФайл слишком большой
BОн содержит секреты, которые попадут в историю репозитория
CGit не поддерживает такие файлы
DЭто замедлит сборку
2. Что даёт NODE_ENV=production в Express?
AОтключает базу данных
BВключает оптимизации и ускоряет приложение
CУдаляет все маршруты
DДелает код синхронным