This guide covers environment setup, configuration management, and deployment environment variables for OpenLN.
OpenLN uses different configurations for different environments:
- Development: Local development with hot reloading
- Testing: Automated testing environment
- Staging: Pre-production environment for testing
- Production: Live application environment
project-root/
βββ client/
β βββ .env.local # Local development (gitignored)
β βββ .env.development # Development defaults
β βββ .env.staging # Staging environment
β βββ .env.production # Production environment
β βββ .env.example # Template file
βββ server/
β βββ .env # Local development (gitignored)
β βββ .env.development # Development defaults
β βββ .env.staging # Staging environment
β βββ .env.production # Production environment
β βββ .env.example # Template file
βββ .gitignore
.env.local(highest priority, always loaded).env.[mode](e.g.,.env.development).env(lowest priority)
# API Configuration
VITE_API_URL=http://localhost:5000/api/v1
VITE_SERVER_URL=http://localhost:5000
# Environment
VITE_NODE_ENV=development
# Feature Flags
VITE_ENABLE_ANALYTICS=false
VITE_ENABLE_MOCKING=true
# Debug Settings
VITE_DEBUG_MODE=true
VITE_LOG_LEVEL=debug# API Configuration
VITE_API_URL=https://api-staging.openln.dev/api/v1
VITE_SERVER_URL=https://api-staging.openln.dev
# Environment
VITE_NODE_ENV=staging
# Feature Flags
VITE_ENABLE_ANALYTICS=true
VITE_ENABLE_MOCKING=false
# Debug Settings
VITE_DEBUG_MODE=true
VITE_LOG_LEVEL=info
# Sentry (Error Tracking)
VITE_SENTRY_DSN=your-staging-sentry-dsn
# Analytics
VITE_GOOGLE_ANALYTICS_ID=GA-STAGING-ID# API Configuration
VITE_API_URL=https://api.openln.dev/api/v1
VITE_SERVER_URL=https://api.openln.dev
# Environment
VITE_NODE_ENV=production
# Feature Flags
VITE_ENABLE_ANALYTICS=true
VITE_ENABLE_MOCKING=false
# Debug Settings
VITE_DEBUG_MODE=false
VITE_LOG_LEVEL=error
# Sentry (Error Tracking)
VITE_SENTRY_DSN=your-production-sentry-dsn
# Analytics
VITE_GOOGLE_ANALYTICS_ID=GA-PRODUCTION-ID
# CDN Configuration
VITE_CDN_URL=https://cdn.openln.dev# Override any environment variables for local development
# This file is gitignored and should contain sensitive data
# Google AI (for local testing)
VITE_GOOGLE_AI_API_KEY=your-local-api-key
# Local feature overrides
VITE_ENABLE_EXPERIMENTAL_FEATURES=true// src/config/environment.ts
export const config = {
apiUrl: import.meta.env.VITE_API_URL,
serverUrl: import.meta.env.VITE_SERVER_URL,
nodeEnv: import.meta.env.VITE_NODE_ENV,
// Feature flags
enableAnalytics: import.meta.env.VITE_ENABLE_ANALYTICS === 'true',
enableMocking: import.meta.env.VITE_ENABLE_MOCKING === 'true',
// Debug settings
debugMode: import.meta.env.VITE_DEBUG_MODE === 'true',
logLevel: import.meta.env.VITE_LOG_LEVEL || 'info',
// External services
sentryDsn: import.meta.env.VITE_SENTRY_DSN,
googleAnalyticsId: import.meta.env.VITE_GOOGLE_ANALYTICS_ID,
} as const;
// Type-safe environment access
export const isDevelopment = config.nodeEnv === 'development';
export const isProduction = config.nodeEnv === 'production';
export const isStaging = config.nodeEnv === 'staging';// src/services/api.ts
import { config } from '../config/environment';
class ApiService {
private baseURL: string;
constructor() {
this.baseURL = config.apiUrl;
}
async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const url = `${this.baseURL}${endpoint}`;
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
}
export const api = new ApiService();# Server Configuration
NODE_ENV=development
PORT=5000
HOST=localhost
# Database
MONGODB_URI=mongodb://localhost:27017/openln-dev
# JWT Configuration
JWT_SECRET=dev-jwt-secret-key-not-for-production
JWT_EXPIRE=24h
# CORS
CLIENT_URL=http://localhost:5173
ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000
# Google AI
GOOGLE_AI_API_KEY=your-development-api-key
# OAuth (Development)
GOOGLE_CLIENT_ID=your-dev-google-client-id
GOOGLE_CLIENT_SECRET=your-dev-google-client-secret
# Email (Development - Optional)
SMTP_HOST=smtp.mailtrap.io
SMTP_PORT=587
SMTP_USER=your-mailtrap-user
SMTP_PASS=your-mailtrap-pass
# Rate Limiting (Relaxed for development)
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=1000
# Logging
LOG_LEVEL=debug
LOG_FILE=logs/development.log
# Debug Settings
DEBUG_MODE=true
ENABLE_REQUEST_LOGGING=true# Server Configuration
NODE_ENV=staging
PORT=5000
# Database
MONGODB_URI=mongodb+srv://username:password@staging-cluster.mongodb.net/openln-staging
# JWT Configuration
JWT_SECRET=staging-jwt-secret-key-32-characters-minimum
JWT_EXPIRE=7d
# CORS
CLIENT_URL=https://staging.openln.dev
ALLOWED_ORIGINS=https://staging.openln.dev
# Google AI
GOOGLE_AI_API_KEY=your-staging-api-key
# OAuth
GOOGLE_CLIENT_ID=your-staging-google-client-id
GOOGLE_CLIENT_SECRET=your-staging-google-client-secret
# Email
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASS=your-sendgrid-api-key
# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
# Logging
LOG_LEVEL=info
LOG_FILE=logs/staging.log
# Monitoring
SENTRY_DSN=your-staging-sentry-dsn
# Redis (if using)
REDIS_URL=redis://staging-redis:6379# Server Configuration
NODE_ENV=production
PORT=5000
# Database
MONGODB_URI=mongodb+srv://username:password@production-cluster.mongodb.net/openln-production
# JWT Configuration
JWT_SECRET=super-secure-production-jwt-secret-key-64-characters-minimum
JWT_EXPIRE=7d
# CORS
CLIENT_URL=https://openln.dev
ALLOWED_ORIGINS=https://openln.dev,https://www.openln.dev
# Google AI
GOOGLE_AI_API_KEY=your-production-api-key
# OAuth
GOOGLE_CLIENT_ID=your-production-google-client-id
GOOGLE_CLIENT_SECRET=your-production-google-client-secret
# Email
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASS=your-production-sendgrid-api-key
# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
# Logging
LOG_LEVEL=warn
LOG_FILE=logs/production.log
# Monitoring
SENTRY_DSN=your-production-sentry-dsn
# Redis
REDIS_URL=redis://production-redis:6379
# Security
ENABLE_HELMET=true
ENABLE_RATE_LIMITING=true
TRUST_PROXY=true# Local overrides and sensitive data
# This file is gitignored
# Local database
MONGODB_URI=mongodb://localhost:27017/openln-local
# Local secrets
JWT_SECRET=local-development-secret-key
GOOGLE_AI_API_KEY=your-local-google-ai-key
# Local OAuth (optional)
GOOGLE_CLIENT_ID=your-local-google-client-id
GOOGLE_CLIENT_SECRET=your-local-google-client-secret
# Debug settings
DEBUG=openln:*
NODE_DEBUG=cluster,net,http,fs,tls,module,timers// server/config/environment.js
import dotenv from 'dotenv';
// Load environment file based on NODE_ENV
const envFile = process.env.NODE_ENV ? `.env.${process.env.NODE_ENV}` : '.env';
dotenv.config({ path: envFile });
export const config = {
// Server
nodeEnv: process.env.NODE_ENV || 'development',
port: parseInt(process.env.PORT) || 5000,
host: process.env.HOST || 'localhost',
// Database
mongodbUri: process.env.MONGODB_URI || 'mongodb://localhost:27017/openln',
// JWT
jwt: {
secret: process.env.JWT_SECRET,
expire: process.env.JWT_EXPIRE || '7d',
},
// CORS
cors: {
clientUrl: process.env.CLIENT_URL || 'http://localhost:5173',
allowedOrigins: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:5173'],
},
// External APIs
googleAI: {
apiKey: process.env.GOOGLE_AI_API_KEY,
},
// OAuth
oauth: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
},
// Email
email: {
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT) || 587,
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
// Rate Limiting
rateLimit: {
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 900000,
maxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100,
},
// Logging
logging: {
level: process.env.LOG_LEVEL || 'info',
file: process.env.LOG_FILE || 'logs/app.log',
},
// Monitoring
sentry: {
dsn: process.env.SENTRY_DSN,
},
// Redis
redis: {
url: process.env.REDIS_URL,
},
// Feature flags
features: {
enableHelmet: process.env.ENABLE_HELMET === 'true',
enableRateLimiting: process.env.ENABLE_RATE_LIMITING === 'true',
debugMode: process.env.DEBUG_MODE === 'true',
},
};
// Validation
const requiredEnvVars = [
'MONGODB_URI',
'JWT_SECRET',
];
const missingEnvVars = requiredEnvVars.filter(
envVar => !process.env[envVar]
);
if (missingEnvVars.length > 0) {
throw new Error(
`Missing required environment variables: ${missingEnvVars.join(', ')}`
);
}
export const isDevelopment = config.nodeEnv === 'development';
export const isProduction = config.nodeEnv === 'production';
export const isStaging = config.nodeEnv === 'staging';// server/config/database.js
import mongoose from 'mongoose';
import { config, isDevelopment } from './environment.js';
import { logger } from '../utils/logger.js';
export const connectDatabase = async () => {
try {
const options = {
// Connection settings
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
bufferCommands: false,
bufferMaxEntries: 0,
};
// Development-specific settings
if (isDevelopment) {
mongoose.set('debug', true);
options.maxPoolSize = 5;
}
await mongoose.connect(config.mongodbUri, options);
logger.info(`MongoDB connected: ${mongoose.connection.host}`);
} catch (error) {
logger.error('Database connection error:', error);
process.exit(1);
}
};
// Handle connection events
mongoose.connection.on('connected', () => {
logger.info('MongoDB connected successfully');
});
mongoose.connection.on('error', (err) => {
logger.error('MongoDB connection error:', err);
});
mongoose.connection.on('disconnected', () => {
logger.warn('MongoDB disconnected');
});
// Graceful shutdown
process.on('SIGINT', async () => {
await mongoose.connection.close();
logger.info('MongoDB connection closed due to app termination');
process.exit(0);
});# Production
VITE_API_URL=https://api.openln.dev/api/v1
VITE_NODE_ENV=production
VITE_ENABLE_ANALYTICS=true
VITE_SENTRY_DSN=your-sentry-dsn
# Preview (Staging)
VITE_API_URL=https://api-staging.openln.dev/api/v1
VITE_NODE_ENV=staging
VITE_ENABLE_ANALYTICS=false
{
"buildCommand": "cd client && npm run build",
"outputDirectory": "client/dist",
"installCommand": "cd client && npm install",
"env": {
"VITE_API_URL": "@vite-api-url"
},
"build": {
"env": {
"VITE_API_URL": "https://api.openln.dev/api/v1"
}
}
}# Set via Railway CLI
railway variables set NODE_ENV=production
railway variables set MONGODB_URI="mongodb+srv://..."
railway variables set JWT_SECRET="your-secret"
railway variables set GOOGLE_AI_API_KEY="your-key"
# Or via Railway dashboard# Set via Heroku CLI
heroku config:set NODE_ENV=production
heroku config:set MONGODB_URI="mongodb+srv://..."
heroku config:set JWT_SECRET="your-secret"
heroku config:set GOOGLE_AI_API_KEY="your-key"
# List all config vars
heroku config# Using AWS CLI
aws ssm put-parameter \
--name "/openln/production/mongodb-uri" \
--value "mongodb+srv://..." \
--type "SecureString"
aws ssm put-parameter \
--name "/openln/production/jwt-secret" \
--value "your-secret" \
--type "SecureString"# Dockerfile
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
RUN npm ci --only=production
# Copy source code
COPY . .
# Expose port
EXPOSE 5000
# Start application
CMD ["npm", "start"]# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "5000:5000"
environment:
- NODE_ENV=${NODE_ENV:-production}
- MONGODB_URI=${MONGODB_URI}
- JWT_SECRET=${JWT_SECRET}
env_file:
- .env.production- Never commit
.envfiles with sensitive data - Use different secrets for each environment
- Rotate secrets regularly
- Use a secrets management service for production
- Validate required environment variables on startup
# Environment files
.env
.env.local
.env.*.local
# But allow example files
!.env.example
!.env.*.example
# Logs
logs/
*.log
# Dependencies
node_modules/
# Build outputs
dist/
build/// server/utils/validateEnv.js
import Joi from 'joi';
const envSchema = Joi.object({
NODE_ENV: Joi.string().valid('development', 'staging', 'production').required(),
PORT: Joi.number().default(5000),
MONGODB_URI: Joi.string().uri().required(),
JWT_SECRET: Joi.string().min(32).required(),
GOOGLE_AI_API_KEY: Joi.string().required(),
}).unknown();
export const validateEnvironment = () => {
const { error, value } = envSchema.validate(process.env);
if (error) {
throw new Error(`Environment validation error: ${error.message}`);
}
return value;
};- Node.js 18+ installed
- MongoDB running locally
- Environment files created
- Dependencies installed
- Services accessible
- Staging environment configured
- Environment variables set
- Database connection tested
- SSL certificates configured
- Monitoring enabled
- Production secrets secured
- Database backups configured
- Monitoring and alerting set up
- Error tracking enabled
- Performance monitoring active
- Security headers configured
Proper environment configuration ensures your application runs consistently across all stages of development and deployment. Always prioritize security when handling sensitive configuration data.