How to Use Environment Variables in JavaScript: Complete Guide
Last verified: April 2026
Executive Summary
Environment variables are one of the most fundamental tools in modern JavaScript development, yet they’re often mishandled. Whether you’re building a Node.js backend, a Next.js application, or configuring Electron desktop apps, understanding how to properly access and manage environment variables can mean the difference between a secure, portable application and one that’s brittle and unsafe.
Learn JavaScript on Udemy
This guide covers the complete landscape of environment variable usage in JavaScript across Node.js, browser-based bundlers, and runtime environments. We’ll walk through practical patterns, demonstrate production-ready code, highlight the most common mistakes developers make, and show you exactly how to handle edge cases that trip up even experienced engineers. By the end, you’ll understand not just how to access environment variables, but when to use them, how to validate them, and how to structure your application so these critical configurations remain secure and maintainable.
Main Data Table: Environment Variable Approaches in JavaScript
| Environment | Access Method | Use Case | Security Level |
|---|---|---|---|
| Node.js Runtime | process.env.VAR_NAME |
Server-side configuration, API keys, database URLs | High |
| Build-Time (Webpack/Vite) | process.env.REACT_APP_* |
Frontend public configuration, API endpoints | Medium (only public values) |
| dotenv Package | require('dotenv').config() |
Local development with .env files | Medium (requires .gitignore) |
| Next.js | process.env.NEXT_PUBLIC_* |
Next.js apps with automatic public/private distinction | High |
| Client-Side Browser | Fetch from API endpoint | Client configuration retrieved at runtime | Medium |
Breakdown by Experience Level
Understanding environment variables spans from beginner to advanced patterns:
- Beginner: Simple access via
process.env.KEY, using dotenv locally - Intermediate: Validation schemas, separation of public/private vars, .env file management across environments
- Advanced: Runtime validation with Zod or Joi, secure secrets management, integration with external vault services like HashiCorp Vault
Comparison Section: Environment Variable Approaches
| Approach | Pros | Cons | Best For |
|---|---|---|---|
process.env Direct Access |
Simple, no dependencies, returns string immediately | No type safety, no validation, vulnerable to typos | Quick scripts, non-critical apps |
| dotenv Package | Excellent for local development, widely adopted | Requires .env file management, not for production secrets | Local development, team environments |
| Validation Schema (Zod/Joi) | Type safety, early error detection, clear contract | Adds dependency, slight performance overhead | Production apps, mission-critical systems |
| External Vault (Vault, AWS Secrets Manager) | Enterprise-grade security, audit trails, rotation support | Complex setup, network dependency, higher cost | Enterprise apps, sensitive credentials |
| Config File (JSON/YAML) | Readable, supports complex structures | Harder to secure, environment-specific management overhead | Non-sensitive configuration, local settings |
Key Factors for Using Environment Variables Correctly
1. Understanding Type Coercion and String Conversion
One of the most subtle mistakes developers make: process.env always returns strings. If you set DEBUG=true, accessing process.env.DEBUG === true will be false because the string 'true' is not the boolean true. This catches people constantly. You need to explicitly convert:
const DEBUG = process.env.DEBUG === 'true';
const PORT = parseInt(process.env.PORT, 10);
const TIMEOUT = parseFloat(process.env.TIMEOUT);
2. Validation and Fail-Fast Behavior
Missing environment variables should crash your application during startup, not silently fail during a critical operation. Use a validation schema to catch configuration errors immediately:
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(32),
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'staging', 'production']).default('development')
});
const config = envSchema.parse(process.env);
3. Separation of Public and Private Variables
Public variables (those visible in your compiled frontend code) should never contain secrets. Most modern frameworks enforce this with prefixes like REACT_APP_ or NEXT_PUBLIC_. This prevents accidental bundling of API keys into your client-side code where they’re visible to everyone.
4. Performance Considerations
Reading from process.env is fast, but dereferencing and parsing should happen once during initialization, not repeatedly in hot paths. Create a config object once at startup and import it throughout your app.
5. Error Handling and Edge Cases
Handle three scenarios explicitly: missing variables, invalid values, and empty strings. An empty environment variable is technically present but unusable:
const API_KEY = process.env.API_KEY?.trim();
if (!API_KEY) {
throw new Error('API_KEY environment variable is required and cannot be empty');
}
Historical Trends in Environment Variable Practices
The approach to environment variables in JavaScript has matured significantly. Five years ago, most teams accessed process.env directly throughout their codebase. Today’s best practice is to validate and centralize configuration at startup. The rise of TypeScript adoption also drove the shift toward type-safe environment variable handling using libraries like Zod and Joi. Additionally, frameworks like Next.js and Remix introduced the NEXT_PUBLIC_ and PUBLIC_ conventions, formalizing the public/private distinction that was previously just a social contract. We’ve also seen increased adoption of runtime validation, moving away from the assumption that environment variables are “probably correct.”
Expert Tips Based on Production Patterns
Tip 1: Create a Centralized Config Module
Rather than accessing process.env throughout your codebase, create a single config module that validates and exports all environment variables. This makes it easy to track what configuration your app requires and catches missing variables immediately:
// src/config.ts
import { z } from 'zod';
const schema = z.object({
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url().optional(),
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'staging', 'production']).default('development'),
});
export const config = schema.parse(process.env);
// src/index.ts
import { config } from './config';
app.listen(config.PORT, () => {
console.log(`Server running on port ${config.PORT}`);
});
Tip 2: Use .env.example for Documentation
Commit a .env.example file to your repository showing all required variables and their format. This documents what configuration is needed without exposing actual secrets:
# .env.example
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
REDIS_URL=redis://localhost:6379
API_KEY=your_api_key_here
PORT=3000
NODE_ENV=development
Tip 3: Distinguish Between Build-Time and Runtime Variables
Variables needed during the build process (like API endpoints for pre-rendering) must be available when your bundler runs. Variables needed only at runtime can be injected later, which is more secure for sensitive data. In Next.js, NEXT_PUBLIC_ variables are available at both stages, while regular environment variables are only available at runtime.
Tip 4: Test Configuration Loading
Write a test that verifies your configuration schema. Catch missing or invalid variables during CI, not in production:
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { config } from './config';
describe('Configuration', () => {
const originalEnv = process.env;
beforeEach(() => {
process.env = { ...originalEnv };
});
afterEach(() => {
process.env = originalEnv;
});
it('should throw when DATABASE_URL is missing', () => {
delete process.env.DATABASE_URL;
expect(() => {
// Re-import to trigger validation
require('./config');
}).toThrow();
});
});
Tip 5: Never Commit .env Files
Add .env, .env.local, and .env.*.local to your .gitignore. These files contain actual secrets that should never be version controlled. Use your deployment platform’s secrets management (GitHub Secrets, Vercel Environment Variables, etc.) for production deployments.
FAQ Section
Question 1: How do I access environment variables in a Node.js application?
Use process.env.VARIABLE_NAME to access any environment variable as a string. Remember that all values are strings, so you’ll need to convert types explicitly. For example: const port = parseInt(process.env.PORT || '3000', 10). For a more robust approach, use a validation library like Zod to parse and validate all environment variables at startup, catching errors before your application runs.
Question 2: What’s the difference between REACT_APP_ and NEXT_PUBLIC_ prefixes?
REACT_APP_ is the convention for Create React App and similar Webpack-based bundlers. NEXT_PUBLIC_ is Next.js’s convention. Both serve the same purpose: marking variables as safe to include in client-side bundles. Without these prefixes, the bundler will not include them in the frontend code. Variables without these prefixes are only available in Node.js server code. Never use them for sensitive data like API keys or database credentials.
Question 3: How do I use environment variables locally with dotenv?
Install dotenv with npm install dotenv, create a .env file in your project root, then call require('dotenv').config() at the top of your entry file (before importing other modules that need env vars). Example .env file: DATABASE_URL=localhost:5432. For Next.js, dotenv is automatic—just create .env.local and it will be loaded. Always add .env and .env.local to .gitignore.
Question 4: How do I validate that required environment variables are set?
Use a validation library like Zod, Joi, or Valibot to create a schema that describes your environment requirements. Parse process.env against this schema at startup. If validation fails, your app will crash immediately with a clear error message, preventing silent failures. This approach also provides type safety if using TypeScript. For example: const config = z.object({ API_KEY: z.string() }).parse(process.env) will throw an error if API_KEY is missing.
Question 5: Can I use environment variables in browser-side JavaScript?
Only public environment variables can be used in browser code, and only if they’ve been bundled by your build tool (via prefixes like REACT_APP_ or NEXT_PUBLIC_). You cannot access process.env in browser JavaScript directly—it’s a Node.js construct. For dynamic client-side configuration, fetch it from a server endpoint at runtime. Never put API keys or secrets in browser-accessible variables; those should remain server-side only.
Conclusion
Managing environment variables correctly is a foundational skill that separates robust applications from fragile ones. The core pattern is simple: validate early, centralize configuration, separate public from private variables, and never commit secrets to version control. Start with direct process.env access and a .env.example file for local development. As your application grows, invest in a validation schema using Zod or similar libraries to catch configuration errors at startup rather than during a production incident.
The specific approach depends on your context. Node.js backends use process.env directly. Frontend frameworks like React and Next.js use build-time variable injection with prefix conventions. For sensitive production secrets, consider external vaults rather than environment variables. Test your configuration loading as part of your CI pipeline, and always remember that environment variables are strings—type conversions are your responsibility.
The key takeaway: environment variables are not just a convenience for avoiding hardcoded values. They’re a critical layer of your application’s security and flexibility architecture. Treat them with the same care you’d apply to database design or authentication logic.
Learn JavaScript on Udemy
Related: How to Create Event Loop in Python: Complete Guide with Exam
Related tool: Try our free calculator