Материал, посвящённый переменным окружения в Node.js, перевод которого мы сегодня публикуем, написал Берк Холланд, веб-разработчик, в сферу интересов которого входят JavaScript, Node.js и VS Code. Кроме того, стоит отметить, что у него сложились непростые отношения с Java. Вот его история.

Переменные окружения — одна из фундаментальных конструкций среды Node.js, но почему-то я никогда не стремился научиться правильно ими пользоваться. Возможно, случилось так из-за их названия — «Environment Variables». Это название вызывало у меня нечто вроде посттравматического синдрома, неприятные воспоминания о том, как я пытался добавить путь к домашней директории Java в Windows. Я тогда толком не мог понять, надо ли добавлять этот путь в переменную PATH, в переменную JAVA_HOME, или и туда и туда. Было неясно и то, нужно ли, чтобы в конце этого пути стояла точка с запятой. Собственно говоря, тогда у меня возникал и вопрос о том, почему я использую Java. Как бы там ни было, я наконец нашёл в себе силы и приступил к знакомству с переменными окружения Node.



Если вы пишете для платформы Node.js, и, так же, как и я, неважно — по каким причинам, до сих пор не особенно хорошо знакомы с переменными окружения — предлагаю это исправить.

Переменные окружения в Node.js


В Node переменные окружения могут быть глобальными (как в Windows), но часто они используются в применении к конкретному процессу, в выполнении которого заинтересован разработчик. Например, если у вас имеется веб-приложение, это значит, что в нём могут применяться следующие переменные окружения:

  • HTTP-порт, который будет прослушивать это приложение.
  • Строка подключения к базе данных.
  • Путь к JAVA_HOME, ох, подождите, это мимо. Прошу учесть, что выздоровление требует времени.

В этом контексте переменные окружения, на самом деле, больше похожи на «Параметры конфигурации» (Configuration Settings) — по мне, так это звучит куда лучше, чем «Environment Variables».

Если раньше вы программировали для .NET, возможно, вы знакомы с чем-то вроде файла web.config. Переменные окружения Node играют практически такую же роль, как настройки из web.config — они представляют собой механизм передачи в приложение информации, которую разработчик не хочет жёстко задавать в коде.

Кстати, на тему «hard code» — задания в коде неких значений вместо получений их из внешних источников, хочу поделиться моим собственным твитом.


Цитирую сам себя на пике помешательства

Как же использовать переменные окружения в Node.js-приложениях? Мне пришлось изрядно потрудиться для того, чтобы найти хороший материал по переменным окружения в Node, с непременным условием наличия в этом материале достаточного количества шуток о Java. Такого материала я не нашёл, поэтому решил написать его сам.

Вот несколько способов использования переменных окружения в приложениях для Node.js.

Указание переменных окружения в терминале


Вы можете указывать переменные окружения в терминале, в котором планируется запускать Node. Например, если у вас имеется приложение, использующее Express, и вы хотите передать ему сведения о порте, сделать это можно так:

PORT=65534 node bin/www

Кстати, интересная вещь. Оказывается, самое большое значение, которое может принимать номер порта, это 65535. Как я это узнал? Конечно, нашёл на StackOverflow. Как вообще кто-либо что-либо узнаёт? Но в Node самый большой номер порта — это 65534. Почему? Понятия не имею. Я не могу знать абсолютно всё.

Итак, для использования переменной окружения в коде нужно воспользоваться объектом process.env. Выглядит это так:

var port = process.env.PORT;

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

PORT=65534 DB_CONN="mongodb://react-cosmos-db:swQOhAsVjfHx3Q9VXh29T9U8xQNVGQ78lEQaL6yMNq3rOSA1WhUXHTOcmDf38Q8rg14NHtQLcUuMA==@react-cosmos-db.documents.azure.com:19373/?ssl=true&replicaSet=globaldb" SECRET_KEY=b6264fca-8adf-457f-a94f-5a4b0d1ca2b9  node bin/www

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

Поэтому рассмотрим другой подход, который заключается в применении файлов .env.

Использование файлов .env


Файлы .env предназначены для хранения переменных окружения. Для использования этой технологии достаточно создать в проекте файл с именем .env и внести в него переменные окружения, начиная каждую с новой строки:

PORT=65534
DB_CONN="mongodb://react-cosmos-db:swQOhAsVjfHx3Q9VXh29T9U8xQNVGQ78lEQaL6yMNq3rOSA1WhUXHTOcmDf38Q8rg14NHtQLcUuMA==@react-cosmos-db.documents.azure.com:10255/?ssl=true&replicaSet=globaldb"
SECRET_KEY="b6264fca-8adf-457f-a94f-5a4b0d1ca2b9"

Читать эти значения можно разными способами. Пожалуй, проще всего — с помощью пакета dotenv из npm:

npm install dotenv --save

После установки пакета его нужно подключить к проекту, а затем им можно пользоваться для работы с переменными окружения. Этот пакет найдёт файл .env и загрузит переменные, описанные в нём, в Node. Вот как это выглядит:

//Использование пакета dotenv для чтения переменных из файла .env в Node
require('dotenv').config();
var MongoClient = require('mongodb').MongoClient;

// Обращение к переменным из .env, которые теперь доступны в process.env 
MongoClient.connect(process.env.DB_CONN, function(err, db) {
  if(!err) {
    console.log("We are connected");
  }
});

Кстати, хочу поделиться важным советом. Не заливайте файлы .env на GitHub. В них находятся сведения, не предназначенные для чужих глаз. Не повторяйте моих ошибок.

Итак, пока всё хорошо, но то, о чём мы тут говорили, не очень-то удобно. Подключать dotenv придётся в каждом файле, где надо использовать переменные окружения, и dotenv придётся использовать в продакшне, когда, в сущности, вам это не нужно. Я не отношусь к фанатам развёртывания бесполезного кода в продакшне, хотя, мне кажется, до сих пор я только этим и занимался.

Что же делать? К счастью, вы пользуетесь VS Code (я абсолютно в этом уверен), а это значит, что у вас есть ещё несколько вариантов.

Работа с файлами .env в VS Code


Для начала вы можете установить расширение DotENV для VS Code, которое даст приятную подсветку синтаксиса файлов .env.


Вот как выглядит файл .env без подсветки синтаксиса и с подсветкой

Кроме того, отладчик VS Code, если вы им пользуетесь, предлагает некоторые более удобные возможности по загрузке значений из файлов .env.

Конфигурация запуска VS Code


Отладчик Node.js для VS Code (он устанавливается по умолчанию) поддерживает загрузку файлов .env посредством конфигураций запуска. Подробности о конфигурациях запуска можно почитать здесь.


Создание базовой конфигурации запуска для Node

После того, как вы создадите базовую конфигурацию запуска для Node (щёлкните по значку с шестерёнкой и выберите Node), можно выполнить одно из следующих действий, или сделать и то и другое.

Первый вариант заключается во включении переменных в конфигурационный файл.


Переменные в конфигурационном файле

Это — вполне приемлемый вариант, но меня немного беспокоит то, что каждое значение должно быть строкой. Всё-таки некоторые значения — это числа, а не строки, а в JavaScript есть лишь, скажем так, три основных типа данных, и мне не хотелось бы лишаться одного из них.

Передать переменные окружения в VS Code можно и более простым способом. Мы уже выяснили, что файлы .env — это наши друзья, поэтому вместо того, чтобы вписывать в конфигурационный файл значения переменных, просто укажем там путь к файлу .env.


Путь к файлу .env в конфигурационном файле

До тех пор, пока мы запускаем процесс Node.js из VS Code, файл с переменными окружения будет передаваться этому процессу. При этом нам не придётся втискивать числа в кавычки, делая из них строки, и не придётся разворачивать ненужный код в продакшне. Ну, по крайней мере, вам этого делать не придётся.

Запуск node-скриптов через NPM


Возможно, вы дошли до этого места и подумали о том, что никогда не запускаете Node-проекты командами вида node …, всегда пользуясь npm-скриптами вроде npm start. Конфигурациями запуска VS Code можно пользоваться и в этом случае. Однако, вместо применения стандартного запуска Node, можно настроить задачу Launch Via NPM.


Задача Launch Via NPM

После этого можно настроить значение параметра envFile, указав путь к файлу .env, и параметра runtimeArgs для запуска нужного скрипта. Обычно в качестве скрипта выступает start или debug.


Настройка запуска проекта с помощью npm

Обратите внимание на то, что в package.json нужно добавить, к npm-скрипту, флаг --inspect для того, чтобы VS Code мог подключить к процессу отладчик. В противном случае, хотя задача и запустится, отладчик не сможет сделать ничего полезного.


Флаг --inspect в package.json

Переменные окружения в продакшне


Итак, мы разобрались с тем, как пользоваться переменными окружения в ходе разработки. Вы, вероятнее всего, не будете применять файлы .env в продакшне, да и конфигурация запуска VS Code на сервере особой пользы не принесёт.

В продакшне определение переменных окружения будет зависеть от особенностей используемой платформы. Например, в случае с Azure есть три способа объявления таких переменных и управления ими.

Первый способ заключается в использовании Azure CLI.

az webapp config appsettings set -g MyResourceGroup -n MyApp --settings PORT=65534

Это работает, но выглядит не очень. Ещё один способ — использование веб-портала Azure. Я не часто пользуюсь веб-порталом, но когда это случается, я обращаюсь к нему именно для установки переменных окружения.

Здесь то, что мы называем «переменными окружения», называется «Application Settings».


Настройка переменных окружения в Azure

Ещё один вариант, учитывая то, что вы пользуетесь VS Code, заключается в установке расширения App Service и в настройке вышеописанных Application Settings прямо из редактора.


Настройка переменных окружения из VS Code

Мне нравится делать в VS Code абсолютно всё, и если бы там можно было бы писать электронные письма, я бы так и поступал. Кстати, похоже, моя мечта сбылась.

Итоги


Теперь вы знаете то же, что знаю я (не так много, позволю заметить), и я чувствую, что выполнил свою цель по шуткам на тему Java. Если вдруг их тут недостаточно — вот ещё одна, автор которой неизвестен: «Java — это очень мощный инструмент по превращению XML в стек-трейсы».

Надеемся, этот материал дал возможность восполнить пробелы в знаниях о переменных окружения в Node тем, кто раньше ими не пользовался, а тем, кто уже о них знал, позволил узнать что-нибудь новое о файлах .env, о применении переменных окружения на боевых серверах, и о том, как облегчить себе жизнь, занимаясь разработкой Node-проектов в VS Code. Если тема переменных окружения Node вам интересна — вот одна из наших предыдущих публикаций на эту тему, посвящённая особенностям работы с process.env.

Уважаемые читатели! Как вы решаете проблемы использования переменных окружения Node в ходе разработки и в продакшне?

Комментарии (14)


  1. mayorovp
    15.03.2018 13:57
    -1

    Вот в момент создания env-файла что-то пошло не так. В ноде есть возможность подключить одной строчкой структурированный конфиг в формате json — зачем после этого использовать какой-то env?


    1. vlreshet
      15.03.2018 14:26

      Как вариант — чтобы явно отделить файл конфигурации от кода. Да и читаемость у .env файла, ИМХО, лучше.


    1. k12th
      15.03.2018 14:30

      Чтобы при развертывании приложении в Docker или Azure или что там популярно на этой неделе точно так же читать конфиг из process.env.SOMETHING, а не генерировать этот json на основе переменных (невелик труд, но все-таки).


    1. Staltec
      15.03.2018 14:33

      Вы видимо не поняли концепцию. Модуль dotenv подключает переменные окружения из файла .env в стандартный process.env.

      Т.е. пока вы запускаетесь локально, вы имитируете в удобной форме передачу параметров процессу в виде человеко читаемого текстового файла. А потом, при запуске в продакшн окружении внутри doker-контейнера например или в качестве heroku-application вы уже не будете указывать .env файл и ваш код без изменений получит эти же переменные от среды исполнения по стандартному механизму передачи переменных окружения (doker, heroku или как в примере у автора azure).

      А цепляя config.json с помощью require вы по сути хардкодите у себя параметры в модуле, полностью игнорируя стандартные механизмы передачи конфигурационных данных через переменные окружения.

      Описанный в статье способ, удобен при работе с сервисами деплой кода на которые осуществляется из систем контроля версий. В вашем случае, вы будете вынуждены задеплоить свой условный config.json с приватными данными (токены, авторизация в базах данных, etc) в репозиторий. Иначе у вас не получится передать ваш файл с конфигурацией сервису исполнения. Переменные окружения полностью решают эту проблему.


      1. mayorovp
        15.03.2018 14:38

        Да эту концепцию я как раз понял, но мне почему-то показалось что env-файл на azure тоже отправляется…


        1. Staltec
          15.03.2018 14:42

          Я не заметил чтобы об этом где-то было сказано в статье.


  1. Suvitruf
    15.03.2018 14:22
    -1

    Как вообще кто-либо что-либо узнаёт? Но в Node самый большой номер порта — это 65534. Почему? Понятия не имею. Я не могу знать абсолютно всё.
    RFC 793: под Source Port и Destination Port 16 бит.


    1. mayorovp
      15.03.2018 14:34
      +1

      А дальше? Почему не 65535-то?


      1. Suvitruf
        15.03.2018 15:00

        Ах, он про это. Но, в любом случае, не понимаю, почему предьява именно к Node.js. На Windows, например, порт максимальный 65,534 в принципе, и это не связанно с node.js.


  1. apapacy
    15.03.2018 16:03

    Два раза перечитал статью в поисках шуток про java.
    Про

    Переменные окружения — одна из фундаментальных конструкций среды Node.js,
    можно спросить что значит фундаментальность.
    Если говорить о переменных окружения то наиболее приемлемый кажется вариант с использованием PM2 pm2.keymetrics.io/docs/usage/environment и вцелом запуск под PM2 кажется наиболее приемлемым для продакшна. Т.к. можно запустить приложение на нескольких ядрах, перезапускать приложение при сбоях и при перерасходе памяти.

    А параметры вообще лучше перенести в обычный файл parameters.json, который не хранится в репозитарии а генерируется в диалоге при npm install. (В репозитарии обчыно хранится parameters.dist.json с значениями по умолчанию)


  1. DexterHD
    15.03.2018 19:52
    +2

    Переменные окружения — одна из фундаментальных конструкций среды Node.js, но почему-то я никогда не стремился научиться правильно ими пользоваться

    Одно из фундаментальных понятий ОПЕРАЦИОННОЙ СИСТЕМЫ! Не node.js не Java, операционной мать её системы!!!111 Как объясните мне КАК? Как можно не понимать фундаментальных элементарных вещей?? fuuuuuuu…


  1. jehy
    15.03.2018 19:53

    Адуха какая-то. Ладно, я ещё могу понять тех людей, которые писали про хранение настроек в .ini файлах, там хоть смысл какой-то был.


    Но


    • Ставить отдельный пакет
    • Ставить отдельное расширение для VS Code
    • Писать специальный JSON (который поддерживается только в VS Code)

    Всё для того, чтобы писать настройки в файл с переменными окружения, который обладает только одним уровнем вложенности и не расширяем — это то, что могло родиться только в результате тяжёлой травмы (уж не знаю, от Java, Microsoft или ещё чего).


    Насколько проще, логичнее и более масштабируемо использовать простой config...


    1. akazakou
      15.03.2018 23:12

      Согласен с вами. Автор совершенно не раскрыл как работать с ENV в Node.js За то про VSCode и прочее — половина статьи. Такое ощущение что просто реклама


  1. megahertz
    16.03.2018 07:04

    Обычно задача решается весьма тривиально без регистрации и смс лишних пакетов, возможны вариации под проект (например чтение process.argv, подгрузка config.local.js который в .gitignore и т.п.)

    // config.js
    export default {
      dbConnectionString: process.env.DB_CONNECTION_STRING || 'mongodb://mongo/db',
    };