Пошук уроків, статей та іншого контенту
Налаштуємо .env-файли, безпечне зберігання конфігурації та розділення параметрів між середовищами.
Конфігурація — це параметри, які можуть змінюватися залежно від середовища запуску застосунку:
порт HTTP-сервера;
адреса бази даних;
ключі для підпису токенів;
URL зовнішніх сервісів;
рівень журналювання;
ознака режиму розробки.
Ці значення не варто жорстко записувати в коді. Один і той самий код має запускатися локально, у тестовому середовищі та на production із різними параметрами.
Для доступу до змінних середовища Node.js використовує об’єкт process.env:
console.log(process.env.PORT);Усі значення з process.env мають тип string або undefined. Навіть якщо в конфігурації записано PORT=3000, Node.js отримає рядок "3000".
.envФайл .env містить змінні середовища у форматі ІМʼЯ=ЗНАЧЕННЯ:
PORT=3000
NODE_ENV=development
DATABASE_URL=postgres://localhost:5432/shop
SESSION_SECRET=local-development-secretЗначення можна брати в лапки, якщо воно містить пробіли або спеціальні символи:
APP_NAME="Мій застосунок"Порожні рядки та рядки, що починаються з #, зазвичай використовують для коментарів:
# Налаштування HTTP-сервера
PORT=3000Сам по собі Node.js не завжди автоматично завантажує .env у process.env. У сучасних версіях Node.js це можна зробити вбудованим параметром --env-file.
Параметр
--env-fileдоступний у Node.js починаючи з версії 20.6.0.
Запуск застосунку:
node --env-file=.env app.jsПісля цього значення з .env будуть доступні через process.env.
Замість одного файла для всіх випадків створюють окремі файли:
.env.development
.env.test
.env.productionНаприклад, .env.development:
NODE_ENV=development
PORT=3000
DATABASE_URL=postgres://localhost:5432/shop_development
SESSION_SECRET=local-development-secretА .env.test:
NODE_ENV=test
PORT=4000
DATABASE_URL=postgres://localhost:5432/shop_test
SESSION_SECRET=test-secretЗапуск із потрібною конфігурацією:
node --env-file=.env.development app.jsnode --env-file=.env.test app.jsДля production зазвичай не зберігають .env.production у репозиторії. Секрети передають через змінні середовища платформи розгортання, контейнера або менеджера секретів:
NODE_ENV=production PORT=8080 node app.jsТак код залишається однаковим, а конфігурація змінюється під час запуску.
.envФайли з реальними секретами не повинні потрапляти до Git. Додайте їх до .gitignore:
.env
.env.*
!.env.exampleПравило !.env.example дозволяє зберігати шаблон конфігурації, але ігнорувати конкретні файли середовищ.
Файл .env.example містить лише назви змінних і безпечні приклади:
NODE_ENV=development
PORT=3000
DATABASE_URL=postgres://localhost:5432/database_name
SESSION_SECRET=replace-this-valueТакий файл допомагає іншому розробнику зрозуміти, які параметри потрібно налаштувати:
cp .env.example .env.developmentПісля цього значення в локальному файлі потрібно замінити на актуальні.
Не додавайте до репозиторію:
паролі;
токени доступу;
приватні ключі;
секрети сесій;
ключі API;
production-адреси баз даних, якщо вони містять облікові дані.
Якщо секрет уже потрапив до Git, простого видалення файла недостатньо: значення могло залишитися в історії комітів. Такий секрет потрібно відкликати або замінити.
Не варто читати process.env по всьому застосунку. Краще створити один модуль конфігурації, який:
читає змінні;
перевіряє обов’язкові значення;
перетворює рядки на потрібні типи;
експортує готовий об’єкт.
Приклад структури:
project/
├── .env.development
├── .env.example
├── .gitignore
├── app.js
└── config.jsФайл config.js:
function required(name) {
const value = process.env[name];
if (!value) {
throw new Error(`Не задано обов'язкову змінну середовища: ${name}`);
}
return value;
}
function parsePort(value) {
const port = Number(value);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error('PORT має бути цілим числом від 1 до 65535');
}
return port;
}
const config = Object.freeze({
environment: process.env.NODE_ENV || 'development',
port: parsePort(process.env.PORT || '3000'),
databaseUrl: required('DATABASE_URL'),
sessionSecret: required('SESSION_SECRET')
});
module.exports = config;Файл app.js:
const http = require('node:http');
const config = require('./config');
const server = http.createServer((request, response) => {
response.writeHead(200, {
'Content-Type': 'application/json; charset=utf-8'
});
response.end(JSON.stringify({
message: 'Застосунок працює',
environment: config.environment,
port: config.port
}));
});
server.listen(config.port, () => {
console.log(
`Сервер запущено в середовищі "${config.environment}" на порту ${config.port}`
);
});Файл .env.development:
NODE_ENV=development
PORT=3000
DATABASE_URL=postgres://localhost:5432/shop_development
SESSION_SECRET=local-development-secretЗапуск:
node --env-file=.env.development app.jsЯкщо обов’язкова змінна відсутня або порт має неправильний формат, застосунок завершиться одразу з помилкою. Це краще, ніж запустити сервер, який згодом непередбачувано зламається під час першого звернення до бази даних або автентифікації.
Значення в process.env завжди є рядками:
process.env.PORT === '3000';Тому числові та логічні параметри потрібно перетворювати явно:
const port = Number(process.env.PORT || '3000');
const isDebugEnabled = process.env.DEBUG === 'true';Не використовуйте без перевірки:
const isDebugEnabled = Boolean(process.env.DEBUG);Якщо DEBUG=false, рядок "false" все одно є непорожнім, тому Boolean('false') поверне true.
Для параметрів із фіксованим набором значень корисно виконувати перевірку:
const allowedEnvironments = new Set([
'development',
'test',
'production'
]);
const environment = process.env.NODE_ENV || 'development';
if (!allowedEnvironments.has(environment)) {
throw new Error(`Невідоме середовище: ${environment}`);
}Значення можна передати безпосередньо під час запуску:
PORT=8080 node --env-file=.env.development app.jsЗмінні, передані середовищем операційної системи або командою запуску, зазвичай використовують для перевизначення локальних значень із файла. Це зручно для CI/CD і production, де секрети не потрібно записувати у файл.
Не покладайтеся на порядок змінних без перевірки документації конкретного способу завантаження. Важливо обрати одне зрозуміле правило для проєкту та використовувати його послідовно.
dotenvЯкщо проєкт уже використовує пакет dotenv або потрібно підтримувати старішу версію Node.js, змінні можна завантажувати програмно.
Встановлення:
npm install dotenvЗавантаження .env.development:
const dotenv = require('dotenv');
dotenv.config({
path: '.env.development'
});
console.log(process.env.PORT);dotenv.config() не перезаписує вже встановлені змінні за замовчуванням. Це дозволяє передавати значення ззовні під час запуску.
Важливо завантажити конфігурацію до імпорту модулів, які використовують process.env. У CommonJS це зазвичай означає виклик dotenv.config() на початку головного файла або окремого модуля конфігурації.
Вбудований --env-file та dotenv розв’язують одну задачу. Для нового застосунку на актуальній версії Node.js можна використовувати вбудований механізм і не додавати зайву залежність.
Команди в package.json можна прив’язати до різних файлів конфігурації:
{
"scripts": {
"dev": "node --env-file=.env.development app.js",
"test": "node --env-file=.env.test app.js",
"start": "node --env-file=.env.production app.js"
}
}Тепер запуск має однаковий інтерфейс:
npm run dev
npm test
npm startУ production файл .env.production може не існувати в репозиторії. Команда або платформа розгортання може передати змінні середовища іншим способом.
Погано:
const sessionSecret = 'real-production-secret';Краще:
const sessionSecret = required('SESSION_SECRET');Секрет має надходити із середовища запуску, а не бути частиною початкового коду.
.env до GitПеред першим комітом перевірте:
git statusЯкщо .env уже відстежується Git, додавання до .gitignore саме по собі не припинить відстеження. Файл потрібно прибрати з індексу, не видаляючи локальну копію:
git rm --cached .envЯкщо файл містив справжні секрети, замініть ці секрети після видалення.
Погано:
server.listen(Number(process.env.PORT));Якщо PORT не задано або має неправильне значення, помилка виникне далеко від джерела проблеми. Краще перевірити конфігурацію під час запуску.
process.envНе робіть так:
console.log(process.env);У журналі можуть опинитися паролі, токени та інші секрети. Виводьте лише безпечні параметри:
console.log({
environment: config.environment,
port: config.port
});if (process.env.ENABLE_CACHE) {
// Рядок "false" також потрапить сюди
}Для таких значень використовуйте явне перетворення та перевірку.
Створіть .env.example із переліком усіх необхідних змінних.
Додайте реальні .env-файли до .gitignore.
Створіть окремі конфігурації для development, test і production.
Завантажуйте потрібний файл під час запуску.
Зберіть читання конфігурації в одному модулі.
Перевіряйте обов’язкові значення під час старту.
Явно перетворюйте числа та логічні значення.
Не виводьте секрети в журнали.
Для production передавайте секрети через середовище платформи або менеджер секретів.
process.env містить конфігурацію процесу Node.js.
.env зручний для локальної розробки, але не повинен потрапляти до репозиторію разом із секретами.
.env.example документує необхідні змінні без справжніх секретів.
Для різних середовищ використовують окремі конфігурації та різні способи запуску.
Значення з process.env завжди є рядками, тому їх потрібно перевіряти й перетворювати.
Центральний модуль конфігурації спрощує контроль параметрів і робить помилки помітними під час запуску.
Production-секрети краще передавати через змінні середовища або спеціалізоване сховище секретів.