process.env doesn't show variables outside app.js - node.js

app.js
import koa from 'koa';
import http from 'http';
import logger from 'koa-logger';
import koaBody from 'koa-body';
import dotenv from 'dotenv';
import dotenvExpand from 'dotenv-expand';
const config = dotenv.config();
dotenvExpand(config);
console.log(process.env); // Here I see all data which are in my .env file
import { client } from '#pg'; // Inside this file I doesn't see this , but it still after initializing dotenv
#pg = db/connection/index.js
import { Client } from 'pg';
console.log(process.env.DATABASE_URL, 'fds'); // here I don't see the same ( all variables from .env file are undefined)
export const client = new Client({
connectionString: process.env.DATABASE_URL
});
if you need additional info, pls let me know. Pay attention on my comments inside of code snippets, it can be helpful

You need to invoke dotenv in every file you are calling a .env variable.
import dotenv from 'dotenv';
import { Client } from 'pg';
const config = dotenv.config();
console.log(process.env.DATABASE_URL, 'fds');
If you want to call dotenv in all your app files without calling it every time then you need to require it when you run your app:
node -r dotenv/config app.js

If you don't want to use external packages you can just run your script like this :)
I prefer this method.
"start": "sh -ac '. ./.env; node index.js'"

In order to avoid writing
dotenv.config()
in every file, you could just simply add this line of code in your app.js
dotenv.config({ path: path.resolve(__dirname, "path/to/.env") });

Related

Executing a script upon import in node js, without calling a function

I am trying to import a typescript module that loads my environment variables. since the order of import and executions for my variables is important for my app, I want to refactor them from my index.ts file into another module. However, I don't want to call a function to run my script, but rather I want to execute them upon importing the module which I am doing right now with the loadEnvVars() function.
// index.ts
import loadEnvVars from './settings';
**loadEnvVars();**
import app from './server';
app.run();
Instead of above I want:
// index.ts
import loadEnvVars from './settings';
import app from './server';
app.run();
// ./settings.ts
import dotenv from 'dotenv';
import path from 'path';
const envVars = (): void => {
dotenv.config({
path: path.join(__dirname, '../.env.' + process.env.NODE_ENV),
debug: process.env.NODE_ENV === 'development' ? true : false,
});
};
export default envVars
I myself have come up with the following solution but I am not sure if this is a proper way of doing such imports:
// index.ts
import './settings';
...
// settings.ts
import dotenv from 'dotenv';
import path from 'path';
const envVars = (): void => {
dotenv.config({
path: path.join(__dirname, '../.env.' + process.env.NODE_ENV),
debug: process.env.NODE_ENV === 'development' ? true : false,
});
};
envVars();
export default envVars;
You can make it a bit cleaner by removing the function entirely, since it's no longer used, but called automatically:
// settings.ts
import dotenv from 'dotenv';
import path from 'path';
dotenv.config({
path: path.join(__dirname, '../.env.' + process.env.NODE_ENV),
debug: process.env.NODE_ENV === 'development' ? true : false,
});
// remove this next line
// export default envVars;
That said - depending on import order leads to fragile code. It would be easy to mess up something by accidentally importing something else before importing settings that depends on the config already being set. If I were you, consider continuing to use the original code. Explicit execution dependency timelines are good.
Also note that imports are hoisted. Your code of
import loadEnvVars from './settings';
loadEnvVars();
import app from './server';
is equivalent to
import loadEnvVars from './settings';
import app from './server';
loadEnvVars();

Nestjs: import modules undefined, but methods and functions from modules can be imported

I am using Nestjs with WebStorm & TS 4.2.3^latest.
The problem that I am facing is a bit strange. For example, some modules, like axios can be installed, imported, and used as usual. But some modules, especially Nodejs Core, like fs or path, can't be imported as modules. BUT their methods can be imported and used just fine!
//ERROR: Module undefined on run:dev, but no error in IDE
import path from 'path';
import fs from 'fs';
//Working fine
import { join } from 'path';
import { readFileSync } from 'path';
I am sure, they have correct TS types, even installed manually. For example:
import axios from 'axios';
import path from 'path'; //path is undefined
import { join } from 'path'; // working fine
import { Injectable } from '#nestjs/common';
#Injectable()
export class AppService {
async test(input: string): Promise<void> {
await axios.get() // working fine
await path.join() // Cannot read property 'join' of undefined
//BUT await join() // Works fine!
}
}
I have only one tsconfig.json which is generated by Nest Cli. I am starting my apps via npm start:dev -name and IDE don't show any errors in code, until I ran code.
tsconfig.json module part, just to be sure: "module": "commonjs", package.json doesn't have module part at all.
IDE in this case, misdirect me a bit. Almost forgot, that I am dealing with TS now. Some modules seem to have no default exports, so:
You should import as with them: import * as fs from 'fs';
Or, another option is enabling: "esModuleInterop": true, in your tsconfig.json

Async .mjs works when calling directly, fails when called from another .mjs

I am currently working with the Ring-Client-API and am running into a small issue at the very end of my development. I succesfully created, tested, and ran my RingListener as an individual file, ie by executing RingListener.mjs. My goal is to now start the listener from another file location, and I am running into some issues trying to do that. I am more familiar with CommonJS so please feel free to point me in the right direction for ES6 stuff I am missing. I am running node 14.15.4
Code RingListener.mjs:
import {RingApi} from 'ring-client-api'
import * as dotenv from "dotenv";
dotenv.config({path: '../.env'});
import {readFile, writeFile} from 'fs'
import {promisify} from 'util'
import App from "../objects/GoogleHomeNotification.js";
export async function start() {
const {env} = process;
console.log("Test 1")
const ringApi = new RingApi({my credentials});
console.log("Test 2")
const allCameras = await ringApi.getCameras();
console.log("Test 3")
console.log("Found " + allCameras.length + " camera(s)")
ringApi.onRefreshTokenUpdated.subscribe(
async ({newRefreshToken, oldRefreshToken}) => {
console.log('Refresh Token Updated: ', newRefreshToken)
}
)
if (allCameras.length) {
console.log('Listening for motion and doorbell presses on your cameras.')
}
}
start();
Output for RingListener.mjs
Test 1
Test 2
Test 3
Found 1 camera(s).
Refresh Token Updated: {my token}
Now writing it to proper .env file
Listening for motion and doorbell presses on your cameras.
When I try to start it from my other file, I only reach Test 2.
Start.mjs
import {start} from './objects/RingListener.mjs'
start();
//await start(); //Returns the same results as just start()
Output for Start.mjs
Test 1
Test 2
When running it from another location it seems to get stuck at the first await statement, and I'm not sure why. Any help would be greatly appreciated. I am quite stumped because I am able to actually execute the function and I get the console log statements, but for some reason it keeps failing at the exact same spot with the await call when executed through another file. Is there something I am missing when calling an async function from another file?
Thank you!
EDIT: Thanks #JoshA for pointing me in the right direction for the filepath for dotenv.
The following code now hangs on the "Test 1 Test 2" when I try to import another js module.
import {start} from './objects/RingListener.mjs'
import {default as Webserver} from './app.js'
await start();
Output
Test 1
Test 2
But when I remove my import to the other class it runs perfectly, IE "Test 1, 2, 3, etc".
import {start} from './objects/RingListener.mjs'
//import {default as Webserver} from './app.js'
await start();
Output
Test 1
Test 2
Test 3
Found 1 camera(s).
Refresh Token Updated:
Now writing it to proper .env file
Listening for motion and doorbell presses on your cameras.
I'm not even using it yet and it still is causing it to hang. Eventually I am going to use Webserver.listen(); but the ./app.js just exports the express app.
EDIT: The app.js contains a bunch of variable initialization and express app configuration. Mapping to the different routes on the server. The goal is to remove the app.listen() in the app.js and move it to the Start.mjs and call it by Webserver.listen() from the import.
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var logger = require('morgan');
require('dotenv').config()
/* Variable def here */
var app = express();
// app config here
/* Exports */
module.exports = app;
app.listen(1337, () => {
console.log("Starting server on 1337");
})
I assume you are using dotenv to load your credentials from your .env file which you then pass on to the new RingApi({my credentials}) constructor.
If that's the case, the most likely reason it's failing is that dotenv uses fs.readFileSync to read the .env file which looks for files relative to the path where node js was executed and not relative to the path of the module. Which is why it stops working if you execute the app from the Start.mjs which is in a different path.
If you really want to keep the dotenv config call inside your RingListener.mjs file you can rewrite it to something like this which resolves the absolute path for the .env file.
import { resolve } from 'path';
dotenv.config({path: resolve(__dirname, '../.env')});
If you get an error __dirname is not defined this is because it's not available in ECMAScript modules as documented here.
As a workaround, you can do something like this.
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
// Initialize __dirname
const __dirname = dirname(fileURLToPath(import.meta.url));
// Then use it to resolve path for .env
dotenv.config({path: resolve(__dirname, '../.env')});

Global variables with apollo and express

index.js
import { ApolloServer } from 'apollo-server-express'
import express from 'express'
global.myTest = true
/models/user.js
import Sequelize from 'sequelize'
console.log('test:' + global.myTest)
Anyone knows how to set global variables with Apollo Server Express ? The example above returns undefined.
Due to es6 import module hoisted. All the dependent Modules will be loaded before running any code. You need to make sure to access the global.myTest after it has been defined and assigned. You can use nodejs require keyword to require your user model after assign value to global.myTest.
E.g.
index.js:
import { ApolloServer } from 'apollo-server-express';
import express from 'express';
global.myTest = true;
require('./models/user');
./models/user.js:
console.log('test:' + global.myTest);
The output of the console:
$ npx ts-node ./index.js
test:true

Typescript version of a config.js file

Typically I'll have a config.js file that looks something like this:
config.js
module.exports = {
secret: "sdlfjlsdjkflsdjfsdjflsdjf",
terminal_id: '39493843',
transaction_key: 'asdfsldkfjdslkfjsdl'
};
And I'll use it like this:
index.js
const config = require('./config');
console.log(config.secret);
What is the typescript way of doing this? What does the config.ts file look like and how do I import it in the index.ts file?
A little closer to your original design would be this:
config.ts
export default {
secret: "sdlfjlsdjkflsdjfsdjflsdjf",
terminal_id: '39493843',
transaction_key: 'asdfsldkfjdslkfjsdl'
};
index.ts
import config from './config';
console.log(config.secret);
You don't need to declare the types of your config properties, typescript will infer that from the constants. You still get all the type checking goodness and intellisense.
Based on Arpit Solanki's comment, I've done this:
config.ts
export const secret: string = 'sdlfjlsdjkflsdjfsdjflsdjf';
export const terminal_id: string = '39493843',
export const transaction_key: string = 'asdfsldkfjdslkfjsdl';
index.ts
import * as config from './config';
console.log(config.secret);

Resources