> ## Documentation Index
> Fetch the complete documentation index at: https://docs.loremstock.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Securing Environment Variables in Node.js

> Use dotenv to load secrets from .env files, understand what should never be hardcoded, and keep sensitive config out of version control with .gitignore and .env.example.

Environment variables are the secure way to pass configuration and secrets to your Node.js application. Hardcoding database passwords or API keys directly in code is a critical mistake that exposes them in version control. This page teaches you to use `dotenv` properly, validate variables at startup, and structure your project to never leak secrets.

## What Are Environment Variables

Environment variables are key-value pairs managed at the operating system level. Node.js applications read them through `process.env`. They exist outside your code, which means:

* They differ per deployment (dev, staging, production) without code changes
* They are not committed to version control
* They can be changed without redeploying (with a server restart)

## What Belongs in .env

| Variable         | Example Value                     |
| ---------------- | --------------------------------- |
| `PORT`           | `3000`                            |
| `NODE_ENV`       | `development`                     |
| `DB_URI`         | `mongodb://localhost:27017/myapp` |
| `JWT_SECRET`     | `a-very-long-random-string-here`  |
| `JWT_EXPIRES_IN` | `1d`                              |
| `API_KEY`        | `sk_live_abc123...`               |
| `EMAIL_HOST`     | `smtp.gmail.com`                  |
| `EMAIL_USER`     | `myapp@gmail.com`                 |
| `EMAIL_PASS`     | `app-password-here`               |

<Warning>
  Never hardcode these values directly in your JavaScript files. Anyone with access to the git repository, including everyone who ever commits in the future, can read every value in git history, even if you later delete the file.
</Warning>

## Installing and Using dotenv

```bash theme={null}
npm install dotenv
```

Create your `.env` file in the project root:

```env theme={null}
PORT=3000
NODE_ENV=development
DB_URI=mongodb://localhost:27017/myapp
JWT_SECRET=super-secret-key-min-32-chars-long
JWT_EXPIRES_IN=1d
```

Load it at the very top of your entry file (`app.js` or `index.js`):

```javascript theme={null}
// Must be the FIRST line before any other imports that use process.env
require('dotenv').config();

const express = require('express');
const mongoose = require('mongoose');

// Now process.env.DB_URI is available
mongoose.connect(process.env.DB_URI);
```

Access variables anywhere:

```javascript theme={null}
const PORT = process.env.PORT || 3000;
const secret = process.env.JWT_SECRET;
const dbUri = process.env.DB_URI;
```

## .env File Format

```env theme={null}
# Comments start with #
PORT=3000

# No spaces around =
NODE_ENV=development

# Quotes optional for simple values
DB_URI=mongodb://localhost:27017/myapp

# Use quotes for values with spaces
APP_NAME="My Backend API"

# Multi-line values
PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg...\n-----END PRIVATE KEY-----"
```

## .env.example: The Template File

`.env.example` is a copy of `.env` with placeholder values instead of real secrets. It IS committed to git and shows developers what variables they need to configure.

```env theme={null}
# .env.example - Copy this to .env and fill in your values

PORT=3000
NODE_ENV=development

# MongoDB connection string
DB_URI=mongodb://localhost:27017/your-app-name

# JWT configuration
JWT_SECRET=your-long-random-secret-here-minimum-32-chars
JWT_EXPIRES_IN=1d

# Email configuration
EMAIL_HOST=smtp.gmail.com
EMAIL_USER=your-email@gmail.com
EMAIL_PASS=your-app-password
```

Onboarding a new developer:

```bash theme={null}
cp .env.example .env
# Then edit .env and fill in real values
```

## .gitignore: Protecting Secrets

```bash theme={null}
# .gitignore
node_modules/
.env
.env.*
!.env.example
*.log
```

<Warning>
  The `!.env.example` line explicitly ALLOWS .env.example to be committed even though all other `.env.*` files are excluded.
</Warning>

## NODE\_ENV

`NODE_ENV` is the standard variable for controlling application behavior:

| Value         | Meaning                                                                    |
| ------------- | -------------------------------------------------------------------------- |
| `development` | Local dev: verbose logging, detailed errors, hot reload                    |
| `test`        | Running tests: test database, fast (no rate limiting)                      |
| `production`  | Live server: minimal logging, generic error messages, all optimizations on |

```javascript theme={null}
if (process.env.NODE_ENV === 'production') {
  // Hide detailed error messages from clients
  res.status(500).json({ message: 'Internal Server Error' });
} else {
  // Show stack trace in development
  res.status(500).json({ message: err.message, stack: err.stack });
}
```

## Validating Environment Variables at Startup

Fail loudly at startup if required variables are missing. This is better than failing mysteriously later.

```javascript theme={null}
// config/validateEnv.js
const requiredEnvVars = [
  'PORT',
  'NODE_ENV',
  'DB_URI',
  'JWT_SECRET'
];

function validateEnv() {
  const missing = requiredEnvVars.filter(name => {
    const value = process.env[name];
    return !value || value.trim() === '';
  });

  if (missing.length > 0) {
    throw new Error(
      `Server startup failed. Missing required environment variables: ${missing.join(', ')}`
    );
  }

  // Validate JWT secret length
  if (process.env.JWT_SECRET.length < 32) {
    throw new Error('JWT_SECRET must be at least 32 characters long');
  }

  console.log('Environment variables validated successfully');
}

module.exports = validateEnv;
```

```javascript theme={null}
// app.js
require('dotenv').config();
const validateEnv = require('./config/validateEnv');

validateEnv(); // Throws and stops startup if any vars are missing

const express = require('express');
// ... rest of app
```

## Centralized Config Module

Never scatter `process.env.X` throughout your code. Use a config module:

```javascript theme={null}
// config/index.js
require('dotenv').config();
const validateEnv = require('./validateEnv');
validateEnv();

module.exports = {
  port: parseInt(process.env.PORT, 10) || 3000,
  nodeEnv: process.env.NODE_ENV || 'development',
  dbUri: process.env.DB_URI,
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '1d'
  },
  email: {
    host: process.env.EMAIL_HOST,
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASS
  },
  isDev: process.env.NODE_ENV === 'development',
  isProd: process.env.NODE_ENV === 'production',
  isTest: process.env.NODE_ENV === 'test'
};
```

Use it:

```javascript theme={null}
const config = require('./config');
mongoose.connect(config.dbUri);
jwt.sign(payload, config.jwt.secret, { expiresIn: config.jwt.expiresIn });
```

## Production Secret Management

For production, avoid `.env` files on the server. Use:

| Service                 | Description                                              |
| ----------------------- | -------------------------------------------------------- |
| **AWS Secrets Manager** | Stores and rotates secrets, integrates with AWS services |
| **Azure Key Vault**     | Microsoft's managed secret storage                       |
| **HashiCorp Vault**     | Self-hosted or cloud secret management                   |
| **Heroku Config Vars**  | Set via dashboard or CLI: `heroku config:set KEY=value`  |
| **Railway/Render**      | Set via dashboard environment variables                  |

## Key Terms

| Term                     | Definition                                                            |
| ------------------------ | --------------------------------------------------------------------- |
| **Environment variable** | OS-level key-value pair accessible via `process.env`                  |
| **dotenv**               | npm package that reads `.env` files and loads them into `process.env` |
| **process.env**          | Node.js global object containing all environment variables            |
| **NODE\_ENV**            | Conventional variable indicating the deployment stage                 |
| **Secret**               | A sensitive configuration value like a password, key, or token        |
| **.gitignore**           | File listing paths git should never track or commit                   |

## Common Mistakes

<Accordion title="Committing .env to git">
  This is the most critical mistake. If your .env is in git history, rotate ALL secrets immediately. Simply deleting the file does not remove it from history.
</Accordion>

<Accordion title="Not creating .env.example">
  Without .env.example, new developers don't know what variables to set. They will get mysterious crashes with no guidance.
</Accordion>

<Accordion title="Using process.env directly throughout codebase">
  Scattering process.env.X through 50 files makes refactoring and testing very hard. A config module centralizes and validates everything.
</Accordion>

<Accordion title="Using short JWT secrets">
  JWT\_SECRET='secret' or JWT\_SECRET='password' can be brute-forced. Generate a long random string: node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))'
</Accordion>
