How to use .env file in JavaScript applications with webpack
Tags: showdev, javascript, webpack, webdev
Published: 2020-04-24 19:30:50

It's been awhile I haven't written a post on the blog and, since I must [#StayAtHome](https://staythefuckhome.com/) due to the COVID-19 Pandemic, I would like to write about an interesting topic.

---

## Table of Contents
1. [Introduction](#introduction)
2. [A real example](#a-real-example)
   - [npm init](#npm-init)
   - [webpack, babel and dotenv](#webpack-babel-and-dotenv)
3. [Project structure](#project-structure)
   - [webpack.config.js](#webpackconfigjs)
   - [.env file](#env-file)
   - [src/app.js](#srcappjs)
   - [public/index.html](#publicindexhtml)
4. [Build and serve the app](#build-and-serve-the-app)
5. [Conclusion](#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.

<small>**N.B.** _This must be done **for each** environment, meaning that **.env** files mustn't be committed!_</small>

---

## 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:

```bash
npm init
```

<small>**N.B.** _I'm going to call my app "webpack-env", but you can choose whatever you want: it doesn't matter._</small>

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

```bash
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:

```json 
{
  "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"
  }
}
```
<small>You can copy it and run `npm install` to avoid all the previous steps.</small>

---

## Project structure
At this point we can start crafting our project.

If every worked correctly, you should have two files:
- package.json <small>(created by running `npm init`)</small>
- package-lock.json <small>(created by running `npm install`)</small>

Let's go deeper creating something else.

### webpack.config.js
This is the webpack's configuration file. Just use the following configuration:
```js
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.

```js
const dotenv = require('dotenv').config( {
  path: path.join(__dirname, '.env')
} );
```
We use [dotenv](https://www.npmjs.com/package/dotenv) library to read the **.env** file from the root of the project.

```js
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: 

```js
// `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
<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:

![Project structure](https://dev-to-uploads.s3.amazonaws.com/i/tvk6dl81nhgmin9ofa71.png)

---

## 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](https://www.npmjs.com/package/http-server).

Let's install it:

```bash
npm install http-server --save-dev
```

Once we have installed it, let's define two npm scripts:
- `npm run build` <small>(to build the app)</small>
- `npm run serve` <small>(to serve the app)</small>

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:

```json
"scripts": {
  "build": "webpack --mode=production",
  "serve": "./node_modules/.bin/http-server"
},
```
<small>**N.B.** _Since we don't need to run unit tests, we can remove the `test` scripts._</small>

Now it's possible to compile the app and serve it by running:

```bash
npm run build && npm run serve
```
In your console, you should see something like this:

![Console output](https://dev-to-uploads.s3.amazonaws.com/i/bk1gyleyf1nrlfftcqsd.png)

If everything went good, we should see our application working; just open the url provided by http-server.
![Working app](https://dev-to-uploads.s3.amazonaws.com/i/ag6xfi9qpo47g4ufh3fc.png)

---

## 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

- 👾 [GitHub](https://github.com/Sanfra1407)
- 📷 [Instagram](https://www.instagram.com/Sanfra1407/)

_If you liked the post, you might offer me a_ ☕️ _on [PayPal](https://www.paypal.me/giuseppesanfrancesco)._ 🙂

---