Set environment variables outside of pages dir in nextjs? - javascript

Is there a way to set env variables outside of a pages directory in NextJS? I have a util function that provides the base API route in one place. However, since this is a util function it's not defined on a page. I'd like to avoid having to create a getStaticProps on every page to get my private env variables if possible.
Any thoughts?
utils/index.ts (not on a nextJS page)
export const apiAddress =
process.env.NEXT_PUBLIC_CURRENT_ENV === 'PRODUCTION'
? process.env.PROD_API
: process.env.DEV_API;
...

I had a similar issue where I couldn't access my env vars in the nextjs application.
The solution was to read them in the next.config.js and pass them implicitly.
My example loads the environment from /environments/env.development but you can use any location you want.
So my next next.config.js looks like this:
const path = require('path')
const { parsed: localEnv } = require('dotenv-safe').config({
allowEmptyValues: false,
path: path.resolve(__dirname, `environments/.env.development`),
})
const nextConfig = {
env: localEnv,
}
module.exports = nextConfig
You should be able to access the ENV vars in your .env file like usually:
process.env.YOUR_VARIABLE
Additionally
Based on environment, you can use a predefined ENV var from your package.json as well which lets you load the right file for your environment.
In your package.json define sth. like that in scriptssection:
"dev:development": "ENV=development node server.js",
"dev:staging": "ENV=staging node server.js"
...
Then you have access to the ENV var and can use it directly for your purposes in next.config.js.
Based on example above:
path: path.resolve(__dirname, `environments/.env.${process.env.ENV}`),

Related

Can I use #next/env package in next.config.js?

I am wanting to access the env config from next.config.js in order to surface some env vars set in .env.local and set some server runtime config based on them.
Is it reasonable in next.config.js to do...
const { loadEnvConfig } = require("#next/env");
const env = loadEnvConfig(".").combinedEnv;
It works, but I can't find any docs that explain how to do this, or if it's a supported package/api.
As documented here: https://nextjs.org/docs/basic-features/environment-variables#test-environment-variables
When you run loadEnvConfig it actually updates process.env to include config that next js uses, so this should work...
const { loadEnvConfig } = require("#next/env");
loadEnvConfig(".")
// process.env.SOMETHING <~~ includes value of SOMETHING set in .env.local

Simple and elegant way to override local file If exists?

Looking for elegant and simple solution to have "local configuration override" files.
The idea is to be able to have local configuration that will not ask to be added to git repository every time.
For that I need to include local.config.js if it exists.
I have global app configuration in config.js with configuration like
export const config = {
API_URL="https://some.host",
}
and config.local.js
export const config = {
API_URL="https://other.address",
}
there's .gitignore:
config.local.js
Difficulty:
I do not want to add a node module to project just for this one thing. I believe there should be an elegant way to do this in one or few lines, but have not found any so far.
Things that I tried:
1.
try {
const {
apiUrl: API_URL,
} = require('./config.local.js');
config. API_URL =apiUrl;
} catch (e) {
}
require does not work inside try{} block.
2.
const requireCustomFile = require.context('./', false, /config.local.js$/);
requireCustomFile.keys().forEach(fileName => {
requireCustomFile(fileName);
});
does not work.
3.
export const config = require('./config.local.js') || {default:'config = {...}'}
does not work.
4.
Using .env and settings environment variable: I need to override whole array of configuration values. Not one by one.
This solution uses process.argv. It is native to node as documented here and does not use .env
It inspects the command values used to start the app. Since these should be different between your local and production environments, it's an easy way to switch with no additional modules required.
command prompt to start your node app:
(this might also be in package.json and incurred via npm start if you're using that approach.)
$ node index.js local
index.js of your node app:
var express = require('express');
var config = require('./config');
if (process.argv[2] === 'local') {
// the 3rd argument provided at startup (2nd index) was 'local', so here we are!
config = require('./config_local');
}
var app = express();
// rest of owl…

How to set an environment variable in config.js of a node.js microservice?

I have a node.js application built using graphql. I need to check an 'environment variable' to see what environment I'm on (development, testing, production). Based on that environment, I need to set a url. I think I have a vague idea of what I need but don't know how to accomplish it.
Currently my config.js file looks something like this:
const configuration = convict({
env: {
format: ['development', 'testing', 'production'],
default: 'development',
arg: 'nodeEnv',
env: 'NODE_ENV'
}
const env = configuration.get('env');
configuration.loadFile(`./config/${env}.json`);
configuration.validate({allowed: 'strict'});
module.exports = configuration.getProperties();
)};
And then in a separate file where I actually need to set the url based on the environment, I need to do so based on the kind of environment (development, test or production) that I'm on. The code in that file would be:
If(env=='development'){
const url = 'abc.def.com/xxx/yyy/zzz/graphql';
}
Else If (env == 'testing'){
const url = 'xxx.yyyy.com/abc/def/ghi/graphql';
}
Else{
const url = '123.abc.com/cdc/def/hhh/graphql';
}
I tried to console.log the env value but when I try the following:
console.log( env );
I get an error: Reference error: env is not defined.
Can someone point me to what I'm missing/doing wrong to access the env variable?
You can access them like this:
process.env.VARIABLE_NAME

Define global variable with webpack

Is it possible to define a global variable with webpack to result something like this:
var myvar = {};
All of the examples I saw were using external file require("imports?$=jquery!./file.js")
There are several way to approach globals:
1. Put your variables in a module.
Webpack evaluates modules only once, so your instance remains global and carries changes through from module to module. So if you create something like a globals.js and export an object of all your globals then you can import './globals' and read/write to these globals. You can import into one module, make changes to the object from a function and import into another module and read those changes in a function. Also remember the order things happen. Webpack will first take all the imports and load them up in order starting in your entry.js. Then it will execute entry.js. So where you read/write to globals is important. Is it from the root scope of a module or in a function called later?
config.js
export default {
FOO: 'bar'
}
somefile.js
import CONFIG from './config.js'
console.log(`FOO: ${CONFIG.FOO}`)
Note: If you want the instance to be new each time, then use an ES6 class. Traditionally in JS you would capitalize classes (as opposed to the lowercase for objects) like
import FooBar from './foo-bar' // <-- Usage: myFooBar = new FooBar()
2. Use Webpack's ProvidePlugin.
Here's how you can do it using Webpack's ProvidePlugin (which makes a module available as a variable in every module and only those modules where you actually use it). This is useful when you don't want to keep typing import Bar from 'foo' again and again. Or you can bring in a package like jQuery or lodash as global here (although you might take a look at Webpack's Externals).
Step 1. Create any module. For example, a global set of utilities would be handy:
utils.js
export function sayHello () {
console.log('hello')
}
Step 2. Alias the module and add to ProvidePlugin:
webpack.config.js
var webpack = require("webpack");
var path = require("path");
// ...
module.exports = {
// ...
resolve: {
extensions: ['', '.js'],
alias: {
'utils': path.resolve(__dirname, './utils') // <-- When you build or restart dev-server, you'll get an error if the path to your utils.js file is incorrect.
}
},
plugins: [
// ...
new webpack.ProvidePlugin({
'utils': 'utils'
})
]
}
Now just call utils.sayHello() in any js file and it should work. Make sure you restart your dev-server if you are using that with Webpack.
Note: Don't forget to tell your linter about the global, so it won't complain. For example, see my answer for ESLint here.
3. Use Webpack's DefinePlugin.
If you just want to use const with string values for your globals, then you can add this plugin to your list of Webpack plugins:
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify("5fa3b9"),
BROWSER_SUPPORTS_HTML5: true,
TWO: "1+1",
"typeof window": JSON.stringify("object")
})
Use it like:
console.log("Running App version " + VERSION);
if(!BROWSER_SUPPORTS_HTML5) require("html5shiv");
4. Use the global window object (or Node's global).
window.foo = 'bar' // For SPA's, browser environment.
global.foo = 'bar' // Webpack will automatically convert this to window if your project is targeted for web (default), read more here: https://webpack.js.org/configuration/node/
You'll see this commonly used for polyfills, for example: window.Promise = Bluebird
5. Use a package like dotenv.
(For server side projects) The dotenv package will take a local configuration file (which you could add to your .gitignore if there are any keys/credentials) and adds your configuration variables to Node's process.env object.
// 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.
var db = require('db')
db.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS
})
Notes
Regarding Webpack's Externals, use it if you want to exclude some modules from being included in your built bundle. Webpack will make the module globally available but won't put it in your bundle. This is handy for big libraries like jQuery (because tree shaking external packages doesn't work in Webpack) where you have these loaded on your page already in separate script tags (perhaps from a CDN).
I was about to ask the very same question. After searching a bit further and decyphering part of webpack's documentation I think that what you want is the output.library and output.libraryTarget in the webpack.config.js file.
For example:
js/index.js:
var foo = 3;
var bar = true;
webpack.config.js
module.exports = {
...
entry: './js/index.js',
output: {
path: './www/js/',
filename: 'index.js',
library: 'myLibrary',
libraryTarget: 'var'
...
}
Now if you link the generated www/js/index.js file in a html script tag you can access to myLibrary.foo from anywhere in your other scripts.
Use DefinePlugin.
The DefinePlugin allows you to create global constants which can be
configured at compile time.
new webpack.DefinePlugin(definitions)
Example:
plugins: [
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true)
})
//...
]
Usage:
console.log(`Environment is in production: ${PRODUCTION}`);
You can use define window.myvar = {}.
When you want to use it, you can use like window.myvar = 1
DefinePlugin doesn't actually define anything. What it does is replace variables that exist in your bundle code. If the variable doesn't exist in your code, it will do nothing. So it doesn't create global variables.
In order to create a global variable, write it in your code:
window.MyGlobal = MY_GLOBAL;
And use DefinePlugin to replace MY_GLOBAL with some code:
new webpack.DefinePlugin({
'MY_GLOBAL': `'foo'`,
// or
'MY_GLOBAL': `Math.random()`,
}),
Then your output JS will be like this:
window.MyGlobal = 'foo';
// or
window.MyGlobal = Math.random();
But MY_GLOBAL will never actually exist at runtime, because it is never defined. So that's why DefinePlugin has a misleading name.
I solved this issue by setting the global variables as a static properties on the classes to which they are most relevant. In ES5 it looks like this:
var Foo = function(){...};
Foo.globalVar = {};
You may hit this issue, when triing bundle < script > tag js files in some old project.
Do not use webpack for this, it may be even impossible if joining 50+ libraries like jquery and then figuring out all global variables or if they used nested require. I would advice to simply use uglify js instead , which drops all this problems in 2 commands.
npm install uglify-js -g
uglifyjs --compress --mangle --output bundle.js -- js/jquery.js js/silly.js

Cleanest way to declare env vars with Express

I'm trying to find out there the best solution about how to store environment vars for Node using Express. Many tutorials suggest to move the data into a .json file, here is an example but the don't config environment vars, they define new vars such as:
{
"test": {
"facebook_app_id": "facebook_dummy_dev_app_id",
"facebook_app_secret": "facebook_dummy_dev_app_secret",
},
"development": {
"facebook_app_id": "facebook_dummy_dev_app_id",
"facebook_app_secret": "facebook_dummy_dev_app_secret",
},
"production": {
"facebook_app_id": "facebook_dummy_prod_app_id",
"facebook_app_secret": "facebook_dummy_prod_app_secret",
}
}
The problem I've find to this approach is that I can't define vars in it, because all elements must be inside double quotes ( " ) so I can't do:
{
process.env.PORT = 3000,
process.env.NODE_APP = XXX
....
}
I've tried to do some tricky things like defining a .js file and store there the vars, something like this:
module.exports = {
env: {
USER:"www-data",
NODE_ENV:"development",
PORT:"3002",
....
LOG_FILE:"error.log"
}
}
In this case, I have not found the way to use those vars inside env, something like:
module.exports = {
env: {
...
process.env.APP_NAME:"application.name",
process.env.NODEJS_DIR:"/var/nodejs",
process.env.APP_DIR: `${this.NODEJS_DIR}/${this.APP_NAME}/current`, //Not working
process.env.APP_DIR:process.env.NODEJS_DIR}/${this.APP_NAME}/current`, //Not working, and verbose, ugly code
....
}
}
Is there a better way to solve this?
Thanks
You can use dotenv module to achieve this.
So in your root directory, create 2 files (depending on how many different environments you might have) named development.env and production.env
In your app, do this:
var env = process.env.NODE_ENV || 'development';
require('dotenv').config({
path: './' + env + '.env'
});
Then you could access all the environment variables you have defined in the respective env file.
Something like this you are looking for.
app.configure('production', function(){
// production env. specific settings
});
As another example of forming the config file
// config file in /config/general.js
var path = require('path'),
rootPath = path.normalize(__dirname + '/..'),
env = process.env.NODE_ENV || 'development';
if (env == 'development') {
process.env.DEBUG = 'http';
}
var config = {
development: {
name : 'development',
root: rootPath,
port: 3000
},
production: {
name : 'prod',
root: rootPath,
port: 3000
}
};
module.exports = config[env];
Think about the dev-ops team, how they will deploy your software? They won't touch the code, and the configuration file should not contain any logic. So the .js file containing configurations is wrong. You have to provide them an interface to configure the execution. That may be a parameter, or if the configuration is complex, then it should be extracted to a configuration file.
My approach is to load app.json by default. If the dev-ops engeneer provides NODE_ENV=customenv, then load app.customenv.json configuration file. This way the .json file won't expose non-relevant development configurations to the users.
APP_NAME is not part of the environment, but part of your software configuration. This parameter should be configured in the .json configuration file.
Directories can be relevant to the software directory, it defined in the __dirname variable.
The app.json file should be loaded by a configuration module. You can use one of the open-source projects available, for example nconf.

Categories