DEV Community

Cover image for How to use .env file in JavaScript applications with webpack
Giuseppe
Giuseppe

Posted on • Edited on

How to use .env file in JavaScript applications with webpack

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

  1. Introduction
  2. A real example
  3. Project structure
  4. Build and serve the app
  5. Conclusion

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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"
  }
}


Enter fullscreen mode Exit fullscreen mode

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
    } ),
  ],
};


Enter fullscreen mode Exit fullscreen mode

Let's see what we have just written.



const dotenv = require('dotenv').config( {
  path: path.join(__dirname, '.env')
} );


Enter fullscreen mode Exit fullscreen mode

We use dotenv library to read the .env file from the root of the project.



plugins: [
  new webpack.DefinePlugin( {
    "process.env": dotenv.parsed
  } ),
],


Enter fullscreen mode Exit fullscreen mode

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;


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

If everything went good, this should be the final structure:

Project 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


Enter fullscreen mode Exit fullscreen mode

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"
},


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

In your console, you should see something like this:

Console output

If everything went good, we should see our application working; just open the url provided by http-server.
Working app


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)

Collapse
 
malakai profile image
Artyom Ulanchik

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();

Collapse
 
thorstenhirsch profile image
Thorsten Hirsch

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.

Collapse
 
jeromediver profile image
JĂ©rĂŽme Lanteri

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).

Collapse
 
sanfra1407 profile image
Giuseppe • Edited

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.

Collapse
 
seansingoor profile image
HolenGuing • Edited

Thanks for the practical article.
Is it possible to have multiple .env files for different environments?

Collapse
 
sanfra1407 profile image
Giuseppe

I guess yes: you just need to use webpack-merge, create different webpack config files which read different .env files.

Collapse
 
ironsandisme profile image
CĂ©sar Rodriguez

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.

Collapse
 
phreakphreak profile image
Michael Zabala (Mike)

there is currently a dotenv-webpack package that allows you to have your .env file at the root of your project