Programowe ustawienie zmiennych środowiskowych

0

Witam, Z JavaScript dopiero zaczynam. Chciałem zrobić zmienne środowiskowe za pomocą modułu dotenv-wrapper. robię jak w przykładzie:

import dotEnvWrapper from '@ncodefactory/dotenv-wrapper';

const environment = new dotEnvWrapper();
environment.addNumber('HTTP_PORT', 80);
environment.addNumber('HTTPS_PORT', 443);
environment.addBoolean('USE_SSL', false);

I wyrzuca błąd :

(node:6749) ExperimentalWarning: Importing JSON modules is an experimental feature. This feature could change at any time
file:///home/damian/projNode/src/environment.js:10
const environment = new dotEnvWrapper();
                    ^

TypeError: dotEnvWrapper is not a constructor
    at file:///home/damian/projNode/src/environment.js:10:21
    at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12)
0

Ja bym nie naprawiał tego błędu tylko zmienił paczkę na dotenv, bo ta paczka co podałeś nie jest praktycznie używana. Miała jedynie 17 instalacji w ciągu całego tygodnia i może generować dodatkowe problemy, o których nie będziesz wiedział.

https://www.npmjs.com/package/dotenv

Działa ona w taki sposób, że importuje zmienne środowiskowe z osobnego pliku do process.env jako stringi, więc trzeba byłoby zrobić prostą konwersacje string ---> number i string ----> boolean

import dotenv from 'dotenv';

dotenv.config({
 path: 'ścieżka/do/pliku/.env',
});
HTTP_PORT=80
USE_SSL=false
0

Będzie oK:?
.env

LOG_LEVEL=error
PORT=5201
DEBUG=false

PG_HOST=localhost
PG_PORT=1234
PG_DB=test
PG_USER=test
PG_PASSWORD=test

logg.js


const LogLevel = {
    NONE: 0,
    ERROR: 1,
    WARNING: 2,
    INFO: 3,
    DEBUG: 4,
};
const loglevelStringToNumber = (__in) => {
    switch(__in) {
        case 'NONE':
            return LogLevel.NONE;
        case 'ERROR':
            return LogLevel.ERROR;
        case 'WARNING':
            return LogLevel.WARNING;
        case 'INFO':
            return LogLevel.INFO;
        case 'DEBUG':
            return LogLevel.DEBUG;
        default:
            return LogLevel.NONE;
    }
};

const stringToBoolean= (__in) => {
    switch(__in) {
        case 'true':
            return 0;
        case 'false':
            return 1;
        default:
            return 0;
    }
};


export {loglevelStringToNumber, stringToBoolean}

environment.js


import dotenv from 'dotenv';
import { loglevelStringToNumber, stringToBoolean } from './logg.js';


dotenv.config('/home/damian/projNode/.env');


const env_proj = {
    LOG_LEVEL: loglevelStringToNumber(process.env.LOG_LEVEL),
    DEBUG: stringToBoolean(process.env.DEBUG),
    PORT: Number(String(process.env.PORT)),
    PG_HOST: String(process.env.PG_HOST),
    PG_PORT: Number(String(process.env.PG_PORT)),
    PG_DB: String(process.env.PG_DB),
    PG_USER: String(process.env.PG_USER),
    PG_PASSWORD: String(process.env.PG_PASSWORD)
  };

export default env_proj;

app.js

import environment from './environment.js'


console.log(environment);



wynik:

{
  LOG_LEVEL: 0,
  DEBUG: 1,
  PORT: 5201,
  PG_HOST: 'localhost',
  PG_PORT: 1234,
  PG_DB: 'test',
  PG_USER: 'test',
  PG_PASSWORD: 'test'
}

Rozumie że plik .env docelowo użytkownik ma sobie utworzyć w katalogu z projektem ? Jaką w takim razie trzeba podać ścieżkę w konfiguracji dotenv ?
Druga sprawa to jak dorobić wartości domyślne gdy pliku .env ktoś nie wstawi ?

2

Rozumie że plik .env docelowo użytkownik ma sobie utworzyć w katalogu z projektem ?

Tak. Często te pliki ukrywa się przez .gitignore, bo mogą zawierać jakieś poufne hasła i trzyma się je w głównym folderze projektu obok package.json.

Ja robię w taki sposób, że tworzę przykladowo .env i .env.default. Zwykły plik .env ukrywam w .gitignore, a ten drugi .env.default ma listę wszystkich zmiennych środowiskowych, które są używane w projekcie. Służy on jedynie jako taka informacja, które zmienne środowiskowe można ustawić. Wtedy użytkownik po prostu tworzy plik .env i kopiuje do niego .env.default

# .env.default z listą zmiennych

PORT=
DEBUG=

PG_HOST=
PG_PORT=
PG_DB=
PG_USER=
PG_PASSWORD=
# plik .gitignore

.env            # ukrywamy plik .env
!.env.default   # ale .env.default zostawiamy

Jaką w takim razie trzeba podać ścieżkę w konfiguracji dotenv ?

Przekazujemy ścieżkę do głównego folderu

import dotenv from "dotenv";
import path from "path";

// zmienna __dirname zwraca bieżący katalog i załóżmy, że w przykładzie jest to xxx/src/config/
console.dir(__dirname); 

// z xxx/src/config cofamy się do xxx, które jest głównym katalogiem
const envPath = path.resolve(__dirname, '../..', '.env'); 

// wczytujemy plik .env
dotenv.config({
  path: envPath,
});

Druga sprawa to jak dorobić wartości domyślne gdy pliku .env ktoś nie wstawi ?

JavaScript ma operator ||, który odrzuca wszystkie wartości nie konwertujące się do true.

np.
undefined || "moja wartość" // ustawi moja wartość
"foo" || "bar" // tutaj będzie wartość foo

W Twoim pliku environment.js możesz zrobić coś takiego:

const env_proj = {
  PG_USER: String(process.env.PG_USER) || 'admin', 
  PG_PASSWORD: String(process.env.PG_PASSWORD) || 'admin'
}

W przypadku braku wartości login i hasło do bazy automatycznie ustawi się na "admin".

0

Ok, Dzięki za wyjaśnienie.
Zmniejszyłem trochę swój pierwszy projekt bo chcę podłączyć do do bazy postgreSQL.

.env:

PG_HOST=localhost
PG_PORT=5432
PG_DB=damiandb
PG_USER=damian
PG_PASSWORD=damian

environment.js

import dotenv from 'dotenv';
import path from "path";
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const envPath = path.resolve(__dirname, '../..', '.env'); 

dotenv.config(envPath);

const env = {
    PG_HOST: String(process.env.PG_HOST),
    PG_PORT: Number(String(process.env.PG_PORT)),
    PG_DB: String(process.env.PG_DB),
    PG_USER: String(process.env.PG_USER),
    PG_PASSWORD: String(process.env.PG_PASSWORD)
  };

export default env;

pg_client.js

import env from './environment.js';
import Client  from 'pg';

const createClient = () =>
  new Client({
    user: env.PG_USER,
    host: env.PG_HOST,
    database: env.PG_DB,
    password: env.PG_PASSWORD,
    port: env.PG_PORT,
  });

  
export default createClient;

app.js

import createClient from './pg_client.js';
import env from './environment.js'
const getData = async () => {

  const client = createClient();
  await client.connect();
  try {
        console.log(await client.query('SELECT * FROM public.accounts'));
    } finally {
        await client.end();
    }

};

getData().catch(console.error);


console.log(env);

i nie działa :/
wynik:

amian@damianDB:~/projNode$ npm start

> [email protected] start
> node --experimental-json-modules src/app.js

{
  PG_HOST: 'localhost',
  PG_PORT: 5432,
  PG_DB: 'damiandb',
  PG_USER: 'damian',
  PG_PASSWORD: 'damian'
}
TypeError: Client is not a constructor
    at createClient (file:///home/damian/projNode/src/pg_client.js:5:3)
    at getData (file:///home/damian/projNode/src/app.js:5:18)
    at file:///home/damian/projNode/src/app.js:15:1
    at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12)
damian@damianDB:~/projNode$ 

Jak mam wywołać funkcję getData ? Żeby wynika zapytania wyświetlić w konsoli ?

0

Nie związane z .env. Połączenie klienta do bazy chciałem zrobić w osobnym pliku pg_client.js. Wyżej znajdują się wszystkie pliki z projektu.

0

Okej, ale chodziło mi o to, że klasę Client importujesz z pliku 'pg'. Nigdzie nie wrzuciłeś, tej klasy, ani tego pliku.

Jeśli go dodasz to będziemy kombinować czemu to nie działa.

0

pg to moduł pobrany przez npm: https://www.npmjs.com/package/pg

0

Znalazłem chyba rozwiązanie

Link do dokumentacji https://node-postgres.com/

Zamień

import Client  from 'pg';

na

import { Client } from 'pg';

Może wyskoczyć jeszcze jakiś bład, ale tak naprawimy bieżący problem z TypeError: Client is not a constructor.

0

ok, w ten sposób tylko działa :

import pkg from 'pg';
const {Client} = pkg;
0

w console.log jak poniżej powinno mi zwrócić wartość z zapytania (czyli wywołanie funkcji getData())?:

import createClient from './pg_client.js';

const getData = async () => {
  const client = createClient();
  await client.connect();
  
  const result = await client.query(
      `
      SELECT JSON_BUILD_OBJECT(
          'user_id',
          user_id,
          'username',
          username,
          'password',
          password,
          'email',
          email
      )  AS my_data
      FROM public.accounts
      `
    );
    await client.end();
    return result;    
};


console.log(getData());

W każdym razie zapytanie w bazie działa.

0

No właśnie nie... bo funkcja jest asynchroniczna i zwraca promise.

Nie wiem, czy składnia biblioteki jest prawidłowa, bo jej nie znam, trzeba sprawdzić w dokumentacji, ale żeby zrobić console.log funkcji asynchronicznej to najpierw trzeba ją odpowiednio wywołać, bo w innym wypadku zwróci nam obiekt promise zamiast wartości.

Spróbuj coś takiego zrobić.

const getData = async () => {
  // ...
}

getData().then((value) => {
  console.log(value);
});

Można jeszcze przez async IIFE, ale dziwnie to wygląda

const getData = async () => {
  // ...
}

(async () => {
  console.log(await getData());
})();
0

Zwraca w formacie JSON.

getData().then((value) => {
  console.log(value.rows);
});

1 użytkowników online, w tym zalogowanych: 0, gości: 1