Detailed Explanation of Node.js Environment Variable Configuration
What are Environment Variables?
Environment variables are dynamically named values that can affect the behavior of processes on a computer.
Simply put, environment variables are commonly used to make an application behave differently in different environments (such as development, testing, production).
Environment variables typically store the following types of data:
API service address
Service port
Current environment
Database-related configuration
Some sensitive data, such as keys, tokens, etc.
Still a bit abstract? Let me give you an example:
Imagine you are developing a project that needs to connect to a database. The database's address, port, username, password, and other configuration details differ across environments (e.g., development, testing, production). Therefore, you cannot hardcode these configuration details directly in your code; instead, you must dynamically set them via environment variables.
Environment variables are usually defined in a *.env file, saved in the project root directory.
If there are many environment variables, they are also often placed under the config directory or the env directory.
The downside of .env files is that they have relatively poor readability, lack syntax highlighting, and do not support nesting.
If environment variables are complex, it is recommended to use yml or json files instead.
What is the Common process.env.NODE_ENV?
process.env is the environment variable object in Node.js. All environment variables we add are eventually appended to this object.
NODE_ENV, however, is not an environment variable officially defined by Node.js; it is merely a convention established by the community.
So you can choose not to use NODE_ENV at all and use any other variable name you prefer.
For convenience, we will continue to use NODE_ENV as the environment variable name in the following sections.
How Does a Project Know Its Current Environment?
Generally, when starting a project, the current running environment is specified. For example:
$ export NODE_ENV=production
$ node my-app.jsAt this point, the value of process.env.NODE_ENV is production.
This script will throw an error on Windows because Windows does not support the
exportcommand.
To ensure the project runs correctly on different operating systems, we usually use cross-env to set environment variables.
$ npx cross-env NODE_ENV=production node my-app.jsTheoretically, at this point, you can happily set environment variables. However, writing all environment variables on the command line is definitely not maintainable. We'd rather define values for different environments in separate files.
How Does a Project Know Which Configuration File to Read?
Environment variables are scattered across different configuration files, for example, dev.env and prod.env correspond to the development and production environments respectively.
So how does the project know which configuration file to read?
Common solutions are dotenv and node-config.
Additional Requirements for Frontend
In frontend engineering, not only do we need to read environment variables, but we also need to inject them into static assets during the build process.
Take webpack as an example. During the build, webpack uses DefinePlugin to replace the environment variable strings used in the code with the values defined in the webpack configuration file.
// During configuration
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify("5fa3b9"),
BROWSER_SUPPORTS_HTML5: true,
TWO: "1+1",
"typeof window": JSON.stringify("object"),
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
});
// When using
console.log("Running App version " + VERSION);
Hardcoding environment variables into the webpack configuration file might not be a good solution.
Therefore, the dotenv-webpack plugin is used. This plugin is essentially dotenv + DefinePlugin.
The dotenv-webpack plugin allows you to specify the path from which to read configuration items when initializing.
const dotenv = require("dotenv-webpack");
const { resolve } = require("path");
//...some config...
plugins: [
new dotenv({
path: resolve("env", process.env.NODE_ENV + ".env"),
}),
];
Basic Introduction to dotenv
dotenv provides a simple and convenient way to read .env files.
require("dotenv").config();
console.log(process.env);
Basic Introduction to node-config
node-config allows reading json/yaml format files, solving the problems of missing syntax highlighting and difficulty handling nested relationships in .env files.
Node-config only supports json files by default, but you can install additional packages to support yaml.
It is more powerful, supporting configuration file merging, reading configuration files hierarchically, etc. Here is a specific example:
Assume there is a default.json file under the config folder in the project root:
{
// Customer module configs
"Customer": {
"dbConfig": {
"host": "localhost",
"port": 5984,
"dbName": "customers"
},
"credit": {
"initialLimit": 100,
// Set low for development
"initialDays": 1
}
}
}
And a production.json file:
{
"Customer": {
"dbConfig": {
"host": "production-db.example.com"
}
}
}
You can use the configuration file in your project code like this. config provides methods such as get and has to retrieve values from the configuration file.
const config = require("config");
//...
const dbConfig = config.get("Customer.dbConfig");
db.connect(dbConfig, ...);
if (config.has('optionalFeature.detail')) {
const detail = config.get('optionalFeature.detail');
//...
}
If you specify an environment variable when starting the project, the configuration files will also merge.
$ export NODE_ENV=production
$ node my-app.js
port and dbName will be taken from default.json, while host will be overridden by production.json.
(Because Customer.dbConfig.port and Customer.dbConfig.dbName are not defined in production.json.)
Extension
A formal project may require a series of operations when starting. At this point, you might think: wouldn't it be nice to write these tedious operations into a shell script and then directly execute that script when starting the project? For this, you might need the scripty package. scripty allows you to execute a shell script when running npm run xxx.
A similar tool is npm-run-all, which can run multiple npm scripts simultaneously, which is useful in projects with a monorepo architecture.
Summary
Environment variables are a crucial part of a project, helping us manage configurations across different environments.
In Node.js, we can use
process.envto read environment variables from the runtime context.The community provides tools like
dotenvandnode-configto make reading configuration files easier.Frontend projects need to inject environment variables into static assets during the build process.