This article will teach you how to read environment variables from .env files into multiple Angular environments.
What is the .env file?
The .env file is a plain-text file used to store sensitive data and environment-specific configuration for a software application. Sample file:
GREETING=HELLO WORLD
SECRET=12345
By default, Angular uses the Environment files built into the framework (under src/environments), which are good for runtime but are publicly visible (and thus expose environment secrets). To get around this, we'll create a .env file for each environment (dev, staging, production) and load those variables into the Angular env file.
How are we going to implement this?
- Create an .env file for each environment
- Create a Node.js script that loads those .env files into memory
- Use the script to override Angular environment project files with values from the .env files
- Run the Angular build
This solution adds environment variables to the project before build time. So the latest build has variables baked in.
This solution is suitable if:
- You're doing local development ✅
- You're hosting the application (dist files) yourself ✅
- You're building a Docker image (for any environment) ✅
It is not helpful if you need to add environment variables after the build (through some UI on AWS, GitHub, or elsewhere). ❌
You can grab the full code on this repository.
Implementation
1️⃣ Creating environment files
Create a separate .env file for each environment in the root directory:
.env.development
.env.staging
.env.production
Create a scripts directory inside your root project folder. Then, initialize the npm project within this folder.
> npm init -y
angular-multi-env\scripts\package.json:
{
"name": "scripts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
This will generate a package.json and a package-lock.json file.
Install the dotenv package to load environment variables from the selected .env file. This will generate the node_modules directory.
> npm i dotenv
Then create the index.js file in the scripts directory. This is where we'll write the script.
Finally, update the package.json file and add "type": "module".
2️⃣ Loading environment files within the script
The idea here is that when someone runs a build with a particular environment flag, we'll load the variables from the .env file based on that flag. So if you open a CLI and run:
> npm run build production
The script will look for the .env.production file and read variables only from that file. Likewise for development and staging environments.
We'll use the built-in Node.js command process.argv to read the "environment flag".
Start off by importing the required dependencies:
import dotenv from 'dotenv'; // the package we've just installed
import path from 'node:path'; // native node.js module
import fs from 'node:fs'; // native node.js module
Then read the (env) argument (flag) from CLI:
const environment = process.argv[2];
if (!environment) {
throw new Error(`Missing environment flag: ${environment}!`);
}
We'll also do some validation checks to stop anyone from adding dummy flags:
const allowedEnvs = ['development', 'staging', 'production'];
if (!allowedEnvs.includes(environment.toLowerCase())) {
throw new Error(`Invalid environment selected: ${environment}!`);
}
We need to set up a full environment name (and extension), which will be .env.[flag]:
const envFile = `.env.${environment}`;
Now we have to locate this file on the disc:
const currentWorkingDirectory = process.cwd();
const pathToEnvFile = path.resolve(currentWorkingDirectory, envFile);
if (!pathToEnvFile) {
throw new Error(`Env file not found: ${pathToEnvFile}!`);
}
The process.cwd() is another command built-into Node.js that returns current working directory. For me, it prints:
'D:\\Angular\\angular-multi-env'
So that the path to the .env file (pathToEnvFile) becomes:
D:\Angular\angular-multi-env\.env.development or
D:\Angular\angular-multi-env\.env.staging or
D:\Angular\angular-multi-env\.env.production
Next, we use the dotenv package to load the variables from the .env file by specifying the path:
dotenv.config({
path: pathToEnvFile
});
The environment variables should appear in the process.env object. We can log the variables to the console to confirm that:
console.table({
environment,
isProduction: process.env.production,
greeting: process.env.greeting,
apiBaseURL: process.env.apiBaseURL
});
Quick Demo
angular-multi-env> node scripts/index.js development
┌──────────────┬─────────────────────────────┐
│ (index) │ Values │
├──────────────┼─────────────────────────────┤
│ environment │ 'development' │
│ isProduction │ 'false' │
│ greeting │ 'Hello DEV!' │
│ apiBaseURL │ 'http://localhost:3000/api' │
└──────────────┴─────────────────────────────┘
3️⃣ Load the Angular Environment files
Now we do the same thing for Angular. Locate the Angular environment file from the project folder:
const angularEnvFile = `src/environments/environment.${environment}.ts`
const pathToAngularEnvFile = path.resolve(currentWorkingDirectory, angularEnvFile);
if (!pathToAngularEnvFile) {
throw new Error(`Env file not found: ${pathToAngularEnvFile}!`);
}
4️⃣ Override Angular environment files
Use the Node.js FileSystem module to read the contents of the selected environment as a string.
const currentContent = fs.readFileSync(pathToAngularEnvFile, 'utf8');
If the file is empty, generate content. Otherwise, update it:
let contentToOverride = '';
if (!currentContent) {
// If empty => create new content
const newContent = `
export const environment = {
production: ${process.env.production === 'true'},
apiBaseURL: '${process.env.apiBaseURL}',
greeting: '${process.env.greeting}'
};
`;
contentToOverride = newContent.trim();
} else {
// Update the existing contents of the Angular environment file (using RegEx)
const updatedContent = currentContent
.replace(/production: .*/, `production: ${process.env.production === 'true'},`)
.replace(/apiBaseURL: .*/, `apiBaseURL: '${process.env.apiBaseURL}',`)
.replace(/greeting: .*/, `greeting: '${process.env.greeting}',`);
contentToOverride = updatedContent;
}
// Override the file content
fs.writeFileSync(pathToAngularEnvFile, contentToOverride);
5️⃣ Add build scripts for each environment
Update the package.json of the Angular project. Include scripts to run the environment script (for each environment.ts file you have).
I have three files:
/src/environments/environment.development.ts
/src/environments/environment.staging.ts
/src/environments/environment.production.ts
So I'll add three scripts as prebuild scripts:
"scripts": {
"ng": "ng",
"start": "ng serve",
"prebuild:development": "node scripts/index development",
"prebuild:staging": "node scripts/index staging",
"prebuild:production": "node scripts/index production",
"build:development": "ng build --configuration development",
"build:staging": "ng build --configuration staging",
"build:production": "ng build --configuration production",
}
The way prebuild scripts work is, if you run a build script, e.g.
> npm run build:development
The NPM will run the prebuild:development first. So it will generate/update the environment.development.ts file and then run the selected script.
6️⃣ Final Demo
> npm run build:production
> angular-multi-env@0.0.0 prebuild:production
> node scripts/index production # Loading prebuild script
┌──────────────┬─────────────────────────┐
│ (index) │ Values │
├──────────────┼─────────────────────────┤
│ environment │ 'production' │
│ isProduction │ 'true' │
│ greeting │ 'Hello PROD!' │
│ apiBaseURL │ 'https://myapp.com/api' │
└──────────────┴─────────────────────────┘
Environment file updated!
> angular-multi-env@0.0.0 build:production
> ng build --configuration production
Initial chunk files | Names | Raw size | Estimated transfer size
main-PQGIXNIB.js | main | 224.85 kB | 60.28 kB
polyfills-FFHMD2TL.js | polyfills | 34.52 kB | 11.28 kB
styles-BT3MSNLR.css | styles | 54 bytes | 54 bytes
| Initial total | 259.42 kB | 71.62 kB
Application bundle generation complete. [1.991 seconds]
Update .gitignore file
Don't forget to add .env files and the scripts/node_modules directory to the .gitignore file.
# Environments
.env
.env.*
# Node_modules
/scripts/node_modules
# Also exclude changes from the Angular environment files
/src/environments/*
Once again, you can grab the full code here. If you're interested in learning how to Dockerize an Angular application, you can learn that here.
Bye for now 👋
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.