How to handle different .env files in Next? - node.js

What I want
I have created a new Next project, and I want to manage app behavior according to the NODE_ENV variable. The application must load different variables located in different .env files. eje. if I load NODE_ENV=development, the application should to load the variables located in .env.development file. What is the most efficient and safe way to do it in Next.
What I have
package.json
In the dev script I pass the environment type:
"scripts": {
"dev": "cross-env NODE_ENV=development next",
"build": "next build",
"start": "next start",
},
next.config.js
In the next configuration I load environment variables from correct .env file with dotenv library according to NODE_ENV variable pass in devscript in package.json.
const path = require('path');
const withOffline = require('next-offline');
const webpack = require('webpack');
require('dotenv').config({
path: path.resolve(
__dirname,
`.env.${process.env.NODE_ENV}`,
),
});
module.exports = withOffline({
webpack: (config) => {
// Returns environment variables as an object
const env = Object.keys(process.env).reduce((acc, curr) => {
acc[`process.env.${curr}`] = JSON.stringify(process.env[curr]);
return acc;
}, {});
// Allows you to create global constants which can be configured
// at compile time, which in our case is our environment variables
config.plugins.push(new webpack.DefinePlugin(env));
return config;
},
});
.env.development
TITLE=modo development
pages/index.js
function HomePage() {
return <div>{process.env.TITLE}</div>
}
export default HomePage
With this aproach...
This is the most efficient and safe way to handle diferent .env files in Next?

Nextjs supports env by default without the need to use of webpack.DefinePlugin, just pass it to the env property of next.config.js.
So your code will become:
// next.conf.js
const path = require('path');
const withOffline = require('next-offline');
const webpack = require('webpack');
require('dotenv').config({
path: path.resolve(
__dirname,
`.env.${process.env.NODE_ENV}`,
),
});
module.exports = withOffline({
env: {
VAR_1: process.env.VAR_1
...
// List all the variables that you want to expose to the client
}
});
PAY ATTENTION: these env variables may be exposed to the client side (if you use them in one of your app page).
For example, if your process.env is containing secrets, and by mistake you are using one of them in one of the pages / components that are used by pages, they will be inside js files that are downloaded to the client side.
Since Next.js 9.4, there is a built in .env loading functionality, read about it here https://nextjs.org/docs/basic-features/environment-variables

Agree with the #felixmosh that Nextjs supports env by default without the need to use of webpack.DefinePlugin, but...
It may be seen as limited and confused while loading different configurations on each environment. You can see common problem here
enter link description here
You can solve this problem easily by following these small steps.
You’ll need to create a folder config on the root, with all environment stages you’d like to have.
You can add the initial/common configuration in default.js like this.
API: {
API_URL: process.env.API_URL || '<http://localhost:4000>',
ENDPOINT: '********',
IS_MOCKING_ENABLED: false,
},
}```
Include above config files in the Next.config.js file by publicRuntimeConfig. If you’d like to have it just on the server-side use just serverRuntimeConfig like this.
const APIConfig = config.get('API')
const nextConfig = {
publicRuntimeConfig: {
APIConfig,
},
}
module.exports = nextConfig ```
Usage in any file.
const { publicRuntimeConfig } = getConfig()
const APIConfig = publicRuntimeConfig.APIConfig
[...] ```
Finally, in your package.json. You can inject the environment variables to load the appropriate configuration.
"start:local": "NODE_ENV=development run-p dev"
Reference: enter link description here for detail explanation.

Related

NodeJS + WebPack setting client static data

I have a NodeJS/React/WebPack application that I'm trying to take environment variables that are present at build time and export them as variables that are available to the client without having to request them with AJAX.
What is currently setup is a /browser/index.js file with a method that is exported however the variables are not getting expanded when webpack runs.
function applicationSetup()
{
const config = JSON.parse(process.env.CONFIG);
const APPLICATION_ID = process.env.APPLICATION_ID;
.........
}
During the build process we run node node_modules/webpack/bin/webpack.js --mode production with npm.
What do I need to do in order to expand the environment variable to be their actual values when webpack creates the .js file?
Edit 8/23
I've tried adding it in the webpack.DefinePlugin section of the webpack.config.js file however it's still doesn't seem to be available in the client side code. What am I missing?
Edit #2 (webpack.config.js)
const getClientConfig = (env, mode) => {
return {
plugins: [
new webpack.DefinePlugin({
__isBrowser__: 'false',
__Config__: process.env.CONFIG,
__ApplicationID__:process.env.APPLICATION_ID
})]
}
module.exports = (env, options) => {
const configs = [
getClientConfig(options.env, options.mode)
];
return configs;
};

How to pass webpack env into nodejs application?

I compile my nodejs application with webpack.
And I want to pass variable to my application say version.
so after I run this command:
webpack --env.VERSION=1.2.2
I run my application but I got undefined on console.log({ v: process.env.VERSION });.
How to pass webpack env into nodejs application?
I can't use cross-env or something like that. I want to compile with webpack and having the env variables in my application.
Read the docs for the --env flag (here):
The webpack command line environment option --env allows you to pass in as many environment variables as you like. Environment variables will be made accessible in your webpack.config.js.
You can use the DefinePlugin plugin to have webpack replace strings with the value of your environment variables.
For example, to replace the use of process.env.VERSION in your application code to the value of the environment variable you have set using the --env flag at build time:
plugins: [
new webpack.DefinePlugin({
"process.env.VERSION": JSON.stringify(process.env.VERSION)
})
]
According to documentation on webpack, the env variable will be available in your webpack.config.js like this
const path = require('path');
module.exports = env => {
// Use env.<YOUR VARIABLE> here:
console.log('NODE_ENV: ', env.NODE_ENV); // 'local'
console.log('Production: ', env.production); // true
return {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
};
See here for more details :
https://webpack.js.org/guides/environment-variables/

Run tests using different env. variables depending on a command in package.json

Trying to find a common practice solution of how to run tests depending on test command in package.json with corresponding environment variables from .env file.
.env. //file
BASE_URL=dev
BASE_URL=stage
API_DEV=api-dev
API_STG=api-stage
package.json //file
"test:dev": "mocha ...",
"test:stage": "mocha ...",
Launching test:dev I want to fetch for my tests all variables for DEV environment.
Can I configure it like that?
Lets say your package.json contains the following scripts property-
"test:local": "APP_ENV=local mocha ...",
"test:dev": "APP_ENV=dev mocha ...",
"test:stage": "APP_ENV=stage mocha ...",
"test:prod": "APP_ENV=prod mocha ..."
And lets assume you have the following files: .env for prod, .env.stage for stage and .env.dev for dev environment.
Now in your script you can dynamically load the env files using dotenv package.
switch (process.env.APP_ENV) {
case 'dev':
env_suffix = ".dev";
break;
case 'stage':
env_suffix = ".stage";
break;
case 'local':
env_suffix = ".local";
break;
}
require("dotenv").config({
path: path.join(__dirname + "/../", ".env" + env_suffix)
});
In this way you can have the environment variables in your process.env object
Ok, I solved it like that:
In config folder I have
.env
stage.env
prod.env
and have index.js file where:
const {NODE_ENV = 'dev'} = process.env;
const dotenv = require('dotenv').config({path: `${__dirname}/${NODE_ENV}.env`});
module.exports = {
api: {
url: dotenv.parsed.API_BASE || ''
},
elastic: {
host: dotenv.parsed.ELASTIC_HOST || ''
}
};
and in package json I have:
"elastic:dev": "mocha ./test/elasticsearch/*.js",
"elastic:stage": "NODE_ENV=stage mocha ./test/elasticsearch/*.js",
"elastic:prod": "NODE_ENV=prod mocha ./test/elasticsearch/*.js"
Easy and pretty dry way to start tests aka like a boss depending on the environment.

Set node environment variable to dynamic value in npm script

I would like to set an environment variable dynamically in an npm script.
I'm using cross-env as I'm developing on Windows and the server is Unix-based. I want to initialize an environment variable with the current date (new Date()) so I can access and render it in my create-react-app:
This works (hard-coded string):
"scripts": {
"start": "cross-env-shell REACT_APP_BUILD_DATE=\"currentDate\" react-scripts-ts start",
}
Obviously, currentDate shouldn't be a string but the result of following expression: new Date().
How can I achieve that? In other words: How can evaluate some regular JavaScript and use its result an npm script? Or is this not possible?
I am using simple node script for passing environment variables into called script. It uses child_process.execSync.
// File name: ./build.js
/* eslint-env node */
const execSync = require('child_process').execSync;
const env = Object.create(process.env);
env.REACT_APP_BUILD_DATE= Date.now();
console.log('Used env variables: ' + JSON.stringify(env));
console.log('Run command: react-scripts start');
execSync('react-scripts-ts start', { env: env, stdio: 'inherit' });
Update start command in package.json scripts. like this:
"scripts": {"start": "node ./build.js"}
Just for the record, I'm now using following approach: Write current date to a custom property in package.json and read that value in the app by importing package.json
package.json
"scripts": {
"start": "react-scripts-ts start",
"build": "node ./update-packagejson.js && react-scripts-ts build"
}
update-packagejson.js
const fs = require("fs");
const filePath = "./package.json";
const packageJson = JSON.parse(fs.readFileSync(filePath).toString());
packageJson.ngrvd.buildDate = new Date().toUTCString();
fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2));
Component
import { ngrvd, version } from "../../package.json";
// ...
private static getAppInfo(): string {
const buildDate = process.env.NODE_ENV === "development" ? new Date() : ngrvd.buildDate;
return "Version " + version + " - Built " + moment(buildDate).fromNow();
}
This works on any environment, is simple and understandable and could be extended to also contain other information. When in dev mode I don't write to package.json to prevent having local changes everytime.
For example you want to put build time to your reactjs app. Edit package.json like that:
"scripts": {
"start": "REACT_APP_BUILD_TIME=$(date +%s) react-app-rewired start",
"build": "REACT_APP_BUILD_TIME=$(date +%s) react-app-rewired build"
}
You can use REACT_APP_BUILD_TIME  variable in public/index.html file. For example:
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico?%REACT_APP_BUILD_TIME%" />
You must wrap env variable with % characters. And there is another rule: you must add REACT_APP_ to your env variable. You can not add other env variables to react app.
How can you add all .env variables to reactjs app?
You can use env-cmd package for this.
yarn add env-cmd
"scripts": {
"start": "REACT_APP_BUILD_TIME=$(date +%s) ./node_modules/.bin/env-cmd react-app-rewired start",
"build": "REACT_APP_BUILD_TIME=$(date +%s) ./node_modules/.bin/env-cmd react-app-rewired build"
}
Example .env content:
REACT_APP_NAME="My React App"
REACT_APP_API_ENDPOINT=https://127.0.0.1:8080/api
REACT_APP_SOCKETIO_ENDPOINT=http://127.0.0.1:3333
After that you can add these variables to your public/index.html file like that:
<script>
window.env.REACT_APP_NAME = "%REACT_APP_NAME%";
window.env.REACT_APP_API_ENDPOINT = "%REACT_APP_API_ENDPOINT%";
window.env.REACT_APP_SOCKETIO_ENDPOINT = "%REACT_APP_SOCKETIO_ENDPOINT%";
</script>
In reactjs side you can use these variables like that:
alert(window.env.REACT_APP_SOCKETIO_ENDPOINT);
That's all.
Edit: Normally there is no this property: window.env but we set this now for easy to use. You can assign your env variables to anywhere in index.html file.
In this particular case you'd be better off using shell command instead of JavaScript, so it should be something like the following:
"scripts": {
"start": "cross-env-shell REACT_APP_BUILD_DATE=$(date '+%F %H:%M:%S') react-scripts-ts start",
}
I'd create a custom javascript script doing it for you:
execute.js
var spawn = require('child_process').spawn;
// because first arg will actually be something like "./execute.js"
// this is the "regular javascript" you want to evaluate
var arg1 = process.argv[1];
// so lets eval it
var res = eval(arg1);
// this is the remaining args, that is the command you want to run (and its args)
var command = process.argv[2];
var commandArgs = process.argv.slice(3);
// if arg1 evaluation resulted in a value, append this value to the list of args
if (res) {
commandArgs.push(res);
}
// execute the command
var prc = spawn(command, commandArgs);
and your script definition will become:
"scripts": {
"start": "cross-env-shell ./execute.js \"process.env.REACT_APP_BUILD_DATE = new Date();\" react-scripts-ts start",
}
Or something similar.
This is untested but should get you started on a solution for "evaluate some regular JavaScript and use its result an npm script"
But if you only want to set a date in a env variable, the solution from #bredikhin is better.
ALTERNATIVE SOLUTION TO DEAL WITH ENVIRONMENT VARIABLES
If you can afford to write into an .envfile at the root of your project (by hand or programatically), you can then use dotenv to populate environment variables with it (from dotenv documentation):
// Usage
// As early as possible in your application, require and configure dotenv.
require('dotenv').config()
/* Create a .env file in the root directory of your project. Add environment-specific variables on new lines in the form of NAME=VALUE. For example:
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3
That's it.
process.env now has the keys and values you defined in your .env file.
*/
const db = require('db');
db.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS
});

Test process.env with Jest

I have an application that depends on environmental variables like:
const APP_PORT = process.env.APP_PORT || 8080;
And I would like to test that for example:
APP_PORT can be set by a Node.js environment variable.
or that an Express.js application is running on the port set with process.env.APP_PORT
How can I achieve this with Jest? Can I set these process.env variables before each test or should I mock it somehow maybe?
The way I did it can be found in this Stack Overflow question.
It is important to use resetModules before each test and then dynamically import the module inside the test:
describe('environmental variables', () => {
const OLD_ENV = process.env;
beforeEach(() => {
jest.resetModules() // Most important - it clears the cache
process.env = { ...OLD_ENV }; // Make a copy
});
afterAll(() => {
process.env = OLD_ENV; // Restore old environment
});
test('will receive process.env variables', () => {
// Set the variables
process.env.NODE_ENV = 'dev';
process.env.PROXY_PREFIX = '/new-prefix/';
process.env.API_URL = 'https://new-api.com/';
process.env.APP_PORT = '7080';
process.env.USE_PROXY = 'false';
const testedModule = require('../../config/env').default
// ... actual testing
});
});
If you look for a way to load environment values before running the Jest look for the answer below. You should use setupFiles for that.
Jest's setupFiles is the proper way to handle this, and you need not install dotenv, nor use an .env file at all, to make it work.
jest.config.js:
module.exports = {
setupFiles: ["<rootDir>/.jest/setEnvVars.js"]
};
.jest/setEnvVars.js:
process.env.MY_CUSTOM_TEST_ENV_VAR = 'foo'
That's it.
Another option is to add it to the jest.config.js file after the module.exports definition:
process.env = Object.assign(process.env, {
VAR_NAME: 'varValue',
VAR_NAME_2: 'varValue2'
});
This way it's not necessary to define the environment variables in each .spec file and they can be adjusted globally.
In ./package.json:
"jest": {
"setupFiles": [
"<rootDir>/jest/setEnvVars.js"
]
}
In ./jest/setEnvVars.js:
process.env.SOME_VAR = 'value';
You can use the setupFiles feature of the Jest configuration. As the documentation said that,
A list of paths to modules that run some code to configure or set up
the testing environment. Each setupFile will be run once per test
file. Since every test runs in its own environment, these scripts will
be executed in the testing environment immediately before executing
the test code itself.
npm install dotenv dotenv that uses to access environment variable.
Create your .env file to the root directory of your application and add this line into it:
#.env
APP_PORT=8080
Create your custom module file as its name being someModuleForTest.js and add this line into it:
// someModuleForTest.js
require("dotenv").config()
Update your jest.config.js file like this:
module.exports = {
setupFiles: ["./someModuleForTest"]
}
You can access an environment variable within all test blocks.
test("Some test name", () => {
expect(process.env.APP_PORT).toBe("8080")
})
Expanding a bit on Serhan C.'s answer...
According to the blog post How to Setup dotenv with Jest Testing - In-depth Explanation, you can include "dotenv/config" directly in setupFiles, without having to create and reference an external script that calls require("dotenv").config().
I.e., simply do
module.exports = {
setupFiles: ["dotenv/config"]
}
In test file:
const APP_PORT = process.env.APP_PORT || 8080;
In the test script of ./package.json:
"scripts": {
"test": "jest --setupFiles dotenv/config",
}
In ./env:
APP_PORT=8080
In my opinion, it's much cleaner and easier to understand if you extract the retrieval of environment variables into a utility (you probably want to include a check to fail fast if an environment variable is not set anyway), and then you can just mock the utility.
// util.js
exports.getEnv = (key) => {
const value = process.env[key];
if (value === undefined) {
throw new Error(`Missing required environment variable ${key}`);
}
return value;
};
// app.test.js
const util = require('./util');
jest.mock('./util');
util.getEnv.mockImplementation(key => `fake-${key}`);
test('test', () => {...});
Depending on how you can organize your code, another option can be to put the environment variable within a function that's executed at runtime.
In this file, the environment variable is set at import time and requires dynamic requires in order to test different environment variables (as described in this answer):
const env = process.env.MY_ENV_VAR;
const envMessage = () => `MY_ENV_VAR is set to ${env}!`;
export default myModule;
In this file, the environment variable is set at envMessage execution time, and you should be able to mutate process.env directly in your tests:
const envMessage = () => {
const env = process.env.MY_VAR;
return `MY_ENV_VAR is set to ${env}!`;
}
export default myModule;
Jest test:
const vals = [
'ONE',
'TWO',
'THREE',
];
vals.forEach((val) => {
it(`Returns the correct string for each ${val} value`, () => {
process.env.MY_VAR = val;
expect(envMessage()).toEqual(...
you can import this in your jest.config.js
require('dotenv').config()
this work for me
All the above methods work if you're using require("dotenv").config within the jest.config.js file, a NodeJS application without TypeScript such as what Jialx or Henry Tipantuna has suggested.
But if you're using ts-jest and within the jest.config.ts file.
import dotenv from "dotenv"
dotenv.config()
/* config options below */
When using Typescript the following works for me:
in root:
jest.config.js
/* eslint-disable #typescript-eslint/no-var-requires */
const { pathsToModuleNameMapper } = require('ts-jest');
const { compilerOptions } = require('./tsconfig.paths.json');
module.exports = {
// [...]
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/' }),
};
process.env = Object.assign(process.env, {
env_name: 'dev',
another_var: 'abc123',
});
To build upon #HenryTipantuña's suggestion is to import dotenv in your jest.config.js and use a .env.test file in the config path
require('dotenv').config({
path: '.env.test'
})
Building on top of #jahller's answer.
I made it responsive so you don't need to keep the files in sync as things change.
Put this at the bottom of your jest.config.js file.
const arr = require('fs')
.readFileSync('.env', 'utf8')
.split('\n')
.reduce((vars, i) => {
const [variable, value] = i.split('=')
vars[variable] = value
return vars
}, {})
process.env = Object.assign(process.env, arr)
It reads the contents of your .env file, splits every new line and reduces it all back down to an object where you then assign it to process.env
OR
just use dotenv in jest.setup.js 🤷‍♂️
i have most simple for implementation env (specialy test.env)
require("dotenv").config({ path: './test.env' });
const { sum } = require('./sum.js');
describe('sum', () => {
beforeEach(() => {
jest.resetModules(); // remove cache
})
test('should success', () => {
expect(sum(1, 3)).toEqual(4);
})
})
I think you could try this too:
const currentEnv = process.env;
process.env = { ENV_NODE: 'whatever' };
// test code...
process.env = currentEnv;
This works for me and you don't need module things

Resources