It's been awhile I haven't written a post on the blog and, since I must #StayAtHome due to the COVID-19 Pandemic, I would like to write about an interesting topic.
Table of Contents
Introduction
As you have already read from the title, I'm going to show you how it's possible to read env variables from a .env file in a JavaScript application.
I guess, at this point, that many of you are asking themselves:
"WTF?! Why should I put variables in a file?! It would be better to use them inside the code!"
Well, usually an application could have different variables based on the environment. For instance: on development, staging and production it could have different URLs, different API keys, different users and so on...
So, to do that, you just need to create a .env file in the root of your project, define your variables and read them in your JavaScript code, especially to avoid to change the source code everytime you need to have different configuration.
N.B. This must be done for each environment, meaning that .env files mustn't be committed!
A real example
Let's try to create a simple front end application reading environment variables from a .env file.
npm init
First of all, we need to create the package.json file by running:
npm init
N.B. I'm going to call my app "webpack-env", but you can choose whatever you want: it doesn't matter.
webpack, babel and dotenv
Now we need to install webpack to build our application, babel-loader to compile .js files and dotenv to read and parse the .env file.
npm install webpack webpack-cli @babel/core babel-loader dotenv --save-dev
If you have done everything correct, you should have a package.json like this one:
{
"name": "webpack-env",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.9.0",
"babel-loader": "^8.1.0",
"dotenv": "^8.2.0",
"http-server": "^0.12.1",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
}
}
You can copy it and run npm install
to avoid all the previous steps.
Project structure
At this point we can start crafting our project.
If every worked correctly, you should have two files:
- package.json (created by running
npm init
) - package-lock.json (created by running
npm install
)
Let's go deeper creating something else.
webpack.config.js
This is the webpack's configuration file. Just use the following configuration:
const path = require("path");
const webpack = require('webpack');
const dotenv = require('dotenv').config( {
path: path.join(__dirname, '.env')
} );
module.exports = {
entry: "./src/app.js",
output: {
path: path.resolve(__dirname, "public"),
filename: "app.js",
},
module: {
rules: [
{
test: /\.js?$/,
exclude: /(node_modules)/,
include: path.resolve(__dirname, "src"),
use: {
loader: "babel-loader"
}
},
]
},
plugins: [
new webpack.DefinePlugin( {
"process.env": dotenv.parsed
} ),
],
};
Let's see what we have just written.
const dotenv = require('dotenv').config( {
path: path.join(__dirname, '.env')
} );
We use dotenv library to read the .env file from the root of the project.
plugins: [
new webpack.DefinePlugin( {
"process.env": dotenv.parsed
} ),
],
By using the webpack.DefinePlugin
, we parse and inject the whole .env file's content which is converted into a JavaScript object and assigned to the "process.env"
variable.
From now on, we can use "process.env"
object inside our application.
.env file
Now it's time to add our .env file.
Let's create it in the root of the application with the following variables:
- APP_TITLE = "My application title"
- APP_BASE_URL = "https://foobar.test"
- APP_API_USER = "amazing_webpack"
- APP_ENV = "production"
- APP_TIMEZONE = "Europe/Rome"
src/app.js
This is the source code which will be compiled by webpack:
// `process.env` is the one defined in the webpack's DefinePlugin
const envVariables = process.env;
// Read vars from envVariables object
const {
APP_TITLE,
APP_BASE_URL,
APP_API_USER,
APP_ENV,
APP_TIMEZONE
} = envVariables;
/**
* @const _getRowString
* @description Concatenate `description` and `envVar` for creating a row text.
* @param description
* @param envVar
*
* @returns {string}
*/
const _getRowString = (description, envVar) => {
return `<p>${description}: <strong>${envVar}</strong></p>`;
}
// Append rows to `.env-vars` class
document.querySelector('.env-vars').innerHTML = `
${_getRowString('App title', APP_TITLE)}
${_getRowString('Current environment', APP_ENV)}
${_getRowString('API user', APP_API_USER)}
${_getRowString('Base URL', APP_BASE_URL)}
${_getRowString('Timezone', APP_TIMEZONE)}
`;
// Expose envVariables to the window object
window.envVariables = envVariables;
As defined in webpack.config.js, the final bundle will be put inside the public/ folder => public/app.js.
public/index.html
This file is meant be our app's entry point. It's just a simple HTML file:
<html>
<head>
<title>webpack env</title>
</head>
<body>
<h1>Just some env variables read from a .env file!</h1>
<div class="env-vars"></div>
<script src="app.js"></script>
</body>
</html>
If everything went good, this should be the final structure:
Build and serve the app
Now it's time to compile and serve our application.
First of all we need to install a server to serve our app.
We're going to use http-server.
Let's install it:
npm install http-server --save-dev
Once we have installed it, let's define two npm scripts:
-
npm run build
(to build the app) -
npm run serve
(to serve the app)
We can do it by adding two scripts in the package.json scripts object.
Let's replace the whole scripts object with the following one:
"scripts": {
"build": "webpack --mode=production",
"serve": "./node_modules/.bin/http-server"
},
N.B. Since we don't need to run unit tests, we can remove the test
scripts.
Now it's possible to compile the app and serve it by running:
npm run build && npm run serve
In your console, you should see something like this:
If everything went good, we should see our application working; just open the url provided by http-server.
Conclusion
As you can easily understand, this approach allows you to use variables based on the environment without changing your harcoded variables each time.
You just need to set your env vars, build the app and... that's all!
Follow me on
If you liked the post, you might offer me a ☕️ on PayPal. 🙂
Top comments (8)
Good article, but why we have to define the following?
const dotenv = require('dotenv').config( {
path: path.join(__dirname, '.env')
} );
As per dotenv api docs the following code will automatically seek for
.env
files in the project root folder by default:const dotenv = require('dotenv').config();
Is there a way to only use the .env file if there are no environment variables defined? Like as a fallback solution? Because when moving the app into a docker container, there will be no .env file, but there will be environment variables.
Cia Guiseppe, and thank you for share your knowledge with us.
Two questions/points:
1/ you can share your dotenv content to frontend side by webpack, correct (even if it seems to be considered as a bad practice) ? You didn't talk about that. Can you develop on this point ?
2/ How to handle real variable environment from .env file the best way ? (use case is about to protect key code or any other sugar strings or private datas, but also to handle a variable environment who can change depend of your distribution to install the source code on).
Hi Jérôme,
thank you for you comment. You're right: you shouldn't expose your sensible info (API users, tokens, passwords and so on). This is meant only for sharing silly information and to avoid some harcoded values related to specific envs.
But I'd say yes: is not definitely a best practice to share everything, because being a front end stuff everybody could easily get those info. So I'd suggest to put your private datas one the server side, for sure.
Thanks for the practical article.
Is it possible to have multiple
.env
files for different environments?I guess yes: you just need to use webpack-merge, create different webpack config files which read different .env files.
It didn't work for me. Somehow, Dotenv parses the variables before Webpack, then, when webpack is going to parse, it retrieves an error, because he uses the string content from the env variables as variable names. Stackoverflow didn't helped me, also. I'll just create my own library to do this.
there is currently a dotenv-webpack package that allows you to have your .env file at the root of your project