Facebook Pixel

Managing Multiple Environments in Node.js Development, Staging, and Production

Learn how to manage different environments (development, staging, production) in your Node.js application with environment variables, .env files, and configuration management.

Managing Multiple Environments in Node.js

Every Node.js application goes through multiple environments: development, staging, and production. Each needs different configuration.

The Three Environments

EnvironmentPurposeDatabaseSecretsURL
DevelopmentLocal codingLocal MongoDBDev secretslocalhost:3000
StagingPre-prod testingStaging DBStaging secretsstaging.yourdomain.com
ProductionLive appProduction DBProd secretsapi.yourdomain.com

Using .env Files per Environment

.env.development # Local development .env.staging # Staging server .env.production # Production server .env.example # Template (committed to Git)
// Load the right .env based on NODE_ENV const env = process.env.NODE_ENV || "development"; require("dotenv").config({ path: `.env.${env}` }); console.log(`Running in ${env} mode`); console.log(`Database: ${process.env.MONGODB_URI}`);

.env.development

NODE_ENV=development PORT=3000 MONGODB_URI=mongodb://localhost:27017/devtinder_dev JWT_SECRET=dev_secret_not_for_production CLIENT_URL=http://localhost:5173 LOG_LEVEL=debug

.env.staging

NODE_ENV=staging PORT=3000 MONGODB_URI=mongodb+srv://staging-user:[email protected]/devtinder_staging JWT_SECRET=staging_secret_different_from_dev CLIENT_URL=https://staging.yourdomain.com LOG_LEVEL=info

.env.production

NODE_ENV=production PORT=3000 MONGODB_URI=mongodb+srv://prod-user:[email protected]/devtinder_prod JWT_SECRET=production_secret_long_random_string CLIENT_URL=https://app.yourdomain.com LOG_LEVEL=warn

Using a Configuration Module

Instead of accessing process.env directly everywhere, centralize config:

// src/config/index.js require("dotenv").config(); const config = { env: process.env.NODE_ENV || "development", port: parseInt(process.env.PORT) || 3000, mongoUri: process.env.MONGODB_URI, jwtSecret: process.env.JWT_SECRET, jwtExpiresIn: process.env.JWT_EXPIRES_IN || "7d", clientUrl: process.env.CLIENT_URL, logLevel: process.env.LOG_LEVEL || "info", isDev: process.env.NODE_ENV === "development", isProd: process.env.NODE_ENV === "production", isStaging: process.env.NODE_ENV === "staging" }; // Validate const required = ["mongoUri", "jwtSecret"]; const missing = required.filter(key => !config[key]); if (missing.length > 0) { throw new Error(`Missing config: ${missing.join(", ")}`); } module.exports = config;

Usage:

const config = require("./config"); app.listen(config.port, () => { console.log(`Server running on port ${config.port} in ${config.env} mode`); }); if (config.isDev) { console.log("Development mode verbose logging enabled"); }

Environment-Specific Behavior

// Different cookie settings per environment const cookieOptions = { httpOnly: true, maxAge: 7 * 24 * 60 * 60 * 1000, ...(config.isProd && { secure: true, sameSite: "none" }), ...(config.isDev && { sameSite: "lax" }) }; // Different logging levels if (config.isDev) { app.use(morgan("dev")); // Verbose } else { app.use(morgan("combined")); // Standard } // Different CORS origins const corsOrigins = { development: "http://localhost:5173", staging: "https://staging.yourdomain.com", production: "https://app.yourdomain.com" }; app.use(cors({ origin: corsOrigins[config.env], credentials: true }));

Setting NODE_ENV

Local development:

# In .env.development NODE_ENV=development # Or in package.json scripts "scripts": { "dev": "NODE_ENV=development nodemon src/server.js", "start": "NODE_ENV=production node src/server.js" }

On EC2:

# In .env.production NODE_ENV=production # Or in PM2 ecosystem file module.exports = { apps: [{ name: "devtinder-api", script: "src/server.js", env: { NODE_ENV: "production" } }] };

The Takeaway

Managing multiple environments involves: using separate .env files per environment (.env.development, .env.staging, .env.production), loading the right file based on NODE_ENV, centralizing config in a config module with validation, using environment-specific behavior (cookie settings, CORS, logging), and setting NODE_ENV in package.json scripts or PM2 ecosystem files. Always use different secrets per environment.

Use separate .env files per environment (.env.development, .env.staging, .env.production), load the right one based on NODE_ENV with dotenv config path, centralize all config in a config module, and use environment-specific behavior for cookies, CORS, and logging. Always use different secrets per environment.

Development is for local coding (local DB, debug logging, dev secrets). Staging is for pre-production testing (staging DB, info logging, staging secrets, mirrors production). Production is the live app (production DB, warn logging, strong production secrets, real users).

Create a src/config/index.js module that loads dotenv, defines a config object with all variables (with defaults and type casting), validates required variables, and exports the config. Use config.isDev, config.isProd, config.isStaging for environment-specific behavior.

In .env files (NODE_ENV=development or production), in package.json scripts ("dev": "NODE_ENV=development nodemon src/server.js"), or in PM2 ecosystem files (env: { NODE_ENV: "production" }). Always set it many Express and npm packages behave differently based on NODE_ENV.

Never. Use different JWT secrets, database credentials, and API keys for each environment. If a development secret leaks, your production app stays secure. Use weak/random secrets for development and strong, rotated secrets for production.

Ready to master Node.js completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.