Environment Variables & Configuration
5 questions found
How does the dotenv package work, and what problem does it solve for local Node.js development?
Beginner
dotenv reads key-value pairs from a local .env file and loads them into process.env at application startup, letting developers configure local environment variables (database URLs, API keys) without needing to manually export them in their shell every time -- this file should always be excluded from version control (via .gitignore) since it typically contains secrets, with a checked-in .env.example showing which variables are needed without their actual sensitive values.
// .env file (never committed to version control)
// DATABASE_URL=postgres://localhost:5432/mydb
// API_KEY=abc123
require('dotenv').config();
console.log(process.env.DATABASE_URL); // now available
Real-world example
A new developer joining a project copies .env.example to .env and fills in their own local database credentials, letting dotenv load those values automatically every time they start the application, without needing to modify any actual application code or manually set shell environment variables.
Common follow-ups: Why should .env files never be committed to version control, even for a private repository?;How does dotenv's behavior differ from how environment variables are actually supplied in a production deployment (like Docker or a cloud platform)?
Security;Cloud & DevOps
How would you validate that all required environment variables are present and correctly typed at application startup, rather than discovering a missing one at runtime?
Intermediate
Validating configuration eagerly at startup (using a schema validation library like zod, joi, or envalid) causes the application to fail fast with a clear error message immediately on boot if a required environment variable is missing or malformed, rather than the more dangerous alternative of the application starting successfully but failing unpredictably later, deep inside some unrelated code path, when that specific configuration value is finally accessed.
const { z } = require('zod');
const envSchema = z.object({
DATABASE_URL: z.string().url(),
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'production', 'test']),
});
const env = envSchema.parse(process.env); // throws immediately with a clear error if invalid
Real-world example
A production deployment that was accidentally missing its DATABASE_URL environment variable used to fail confusingly deep inside a database query several minutes after startup; adding zod-based startup validation now makes the process crash immediately with a clear 'DATABASE_URL is required' error the moment it boots, making the root cause obvious right away.
Common follow-ups: Why is failing fast at startup considered better practice than allowing the application to start with invalid or missing configuration?;How would you provide sensible, safe defaults for optional configuration values within this same validation schema?
Error Handling;Node.js Fundamentals & Runtime Architecture
How would you structure application configuration to support multiple environments (development, staging, production) cleanly?
Intermediate
A common pattern centralizes all configuration access through a single config module that reads from process.env once at startup, provides sensible defaults for non-sensitive values, and exports a single typed configuration object used throughout the application -- this avoids scattering direct process.env.X references throughout the codebase (which makes it hard to see what configuration the application actually depends on, and hard to validate consistently).
// config.js -- single source of truth for configuration
module.exports = {
port: parseInt(process.env.PORT, 10) || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
databaseUrl: process.env.DATABASE_URL,
isProduction: process.env.NODE_ENV === 'production',
};
// Elsewhere in the app
const config = require('./config');
app.listen(config.port);
Real-world example
A codebase that previously had process.env.DATABASE_URL scattered across a dozen different files is refactored to route all configuration access through a single config.js module, making it immediately obvious from one file exactly what environment variables the entire application depends on.
Common follow-ups: How does this centralized config pattern make it easier to write a startup validation check covering every required variable?;What's the tradeoff of computing derived config values (like isProduction) once at startup versus checking process.env.NODE_ENV directly wherever needed?
Architecture & Design Patterns;Error Handling
What is the difference between build-time and runtime configuration for a Node.js application, and why does that distinction matter for container-based deployments?
Advanced
Build-time configuration is baked into the application artifact when it's built (like a value substituted during a bundler's build step), meaning a different value requires rebuilding the entire artifact -- runtime configuration is read from the environment when the application actually starts, meaning the exact same built artifact can be deployed unchanged to different environments simply by supplying different environment variables at container startup, which is strongly preferred for anything that needs to differ between staging and production, following the twelve-factor methodology.
// Runtime configuration: read when the container starts, same image works everywhere
const apiUrl = process.env.API_URL;
// Build-time (problematic if it needs to differ per environment): baked in during build
// e.g., a frontend bundler replacing process.env.API_URL at build time
// requires a separate build per environment, which breaks 'build once, deploy everywhere'
Real-world example
A team discovers their frontend build process was baking the API URL in at build time, requiring a completely separate build for staging versus production; refactoring to read the API URL from a runtime-injected configuration value (fetched from a small config endpoint at page load) lets the exact same built artifact be deployed to every environment.
Common follow-ups: Why does 'build once, deploy everywhere' matter for confidence that what was tested in staging is truly identical to what runs in production?;How do frontend applications, which don't have a Node.js runtime environment available in the browser, work around this same build-time versus runtime tension?
Docker & Containerization for Node.js;CI/CD
Publishing & Deployment
What is the risk of accidentally logging or exposing environment variables that contain secrets, and how do you prevent it?
Intermediate
Environment variables containing secrets (database passwords, API keys) can accidentally leak through overly broad logging (like logging the entire process.env object for debugging), error messages that include configuration in a stack trace, or a debug/health endpoint that inadvertently returns configuration details -- prevention includes never logging process.env wholesale, explicitly redacting sensitive keys in any logging middleware, and being deliberate about exactly what a diagnostic endpoint exposes.
// Risky: logs every environment variable, including secrets
console.log(process.env);
// Safer: explicitly allowlist only non-sensitive values for diagnostic logging
console.log({ nodeEnv: process.env.NODE_ENV, port: process.env.PORT });
// A logging library redaction config
const logger = pino({ redact: ['req.headers.authorization', 'password'] });
Real-world example
A security audit discovers that a debug endpoint intended only to show application version information had been calling console.log(process.env) during a previous debugging session, and that log line accidentally shipped to production, exposing database credentials in the centralized logging system until it was caught and removed.
Common follow-ups: How would you audit an existing codebase for accidental instances of logging process.env or other sensitive configuration wholesale?;What redaction features do structured logging libraries like Pino provide to guard against this class of mistake automatically?
Security;Logging & Monitoring