Environment Variables Guide

Complete guide to configuring Migration Script Runner using environment variables

Table of contents

  1. Overview
  2. Configuration Loading (Waterfall)
  3. Type-Safe Environment Variables
  4. Quick Start
    1. Basic Setup
  5. .env File Support (v0.7.0+)
    1. Default Behavior
    2. Basic Example
    3. Environment-Specific Files
      1. Development Setup
      2. Production Setup
    4. Local Overrides
    5. Custom File Names
    6. Disable .env Loading
    7. File Format
    8. Best Practices
  6. Environment Variables
    1. Simple Properties
    2. Complex Objects (Dot-Notation)
      1. Logging Configuration
      2. Backup Configuration
      3. Transaction Configuration
    3. Complex Objects (JSON)
    4. File Patterns
  7. Configuration Files
    1. JavaScript Config (Recommended)
    2. JSON Config
    3. Custom Config File Location
  8. Platform-Specific Examples
    1. Docker
    2. Kubernetes
    3. GitHub Actions
    4. Cloud Platforms
      1. AWS ECS Task Definition
      2. Heroku
  9. Environment-Specific Strategies
    1. Development (.env)
    2. Staging
    3. Production
  10. Advanced Configuration
    1. Manual Loading with ConfigLoader
    2. Adapter Extensibility with Automatic Parsing
      1. Why Use Automatic Parsing?
      2. Before (Manual Mapping)
      3. After (Automatic Parsing)
      4. How It Works
      5. Supported Types
      6. Example with Nested Objects
      7. Custom Overrides for Special Cases
      8. Benefits
      9. Complete Adapter Example
    3. Using auto-envparse Directly
      1. Why Use auto-envparse?
      2. Basic Usage
      3. Features
      4. Example with Nested Objects
      5. Example with Custom Validation
      6. Advanced Features (v2.1+)
      7. Use Cases
      8. MSR Integration
    4. Validation
  11. Troubleshooting
    1. Environment Variables Not Applied
    2. Config File Not Found
    3. Invalid JSON Format
  12. Complete Example
  13. Reference
  14. Next Steps

Overview

MSR v0.5.0+ supports configuration through environment variables, following 12-Factor App principles. This enables:

  • Environment-specific configuration without code changes
  • Container-friendly deployment (Docker, Kubernetes)
  • CI/CD integration with secrets management
  • Production-ready practices for configuration management
  • .env file support (v0.7.0+) for local development and environment-specific overrides

Configuration Loading (Waterfall)

MSR loads configuration using a waterfall approach with clear priority:

1. Built-in defaults     (lowest priority)
   ↓
2. Config file          (msr.config.js/json)
   ↓
3. .env files           (.env.local, .env, env) - v0.7.0+
   ↓
4. Environment variables (MSR_*)
   ↓
5. Constructor overrides (highest priority)

Each level overrides the previous one, allowing flexible configuration strategies.

.env files (v0.7.0+): MSR automatically loads .env files using config.envFileSources. Files are loaded in priority order (first file wins). By default: ['.env.local', '.env', 'env'].


Type-Safe Environment Variables

MSR provides organized enums for type-safe access to environment variable names. Variables are grouped by category (Core, Validation, Logging, Backup, Transaction) with a unified EnvironmentVariables type combining all categories. This is used internally by ConfigLoader and is available for your use:

import { EnvironmentVariables as ENV } from '@migration-script-runner/core';

// Type-safe access to environment variables
const folder = process.env[ENV.MSR_FOLDER];
const dryRun = process.env[ENV.MSR_DRY_RUN];
const tableName = process.env[ENV.MSR_TABLE_NAME];

// Auto-completion and compile-time checking
if (process.env[ENV.MSR_LOGGING_ENABLED] === 'true') {
    console.log(`Logging to: ${process.env[ENV.MSR_LOGGING_PATH]}`);
}

Benefits:

  • Auto-completion - Your IDE will suggest all available environment variable names
  • Compile-time checking - Typos are caught at build time, not runtime
  • Refactoring support - Rename safely across your entire codebase
  • Single source of truth - All environment variable names defined in one place

See Also:


Quick Start

Basic Setup

1. No configuration needed - works out of the box:

import { MigrationScriptExecutor } from '@migration-script-runner/core';
import { MyDatabaseHandler } from './database-handler';

const handler = new MyDatabaseHandler();
const executor = new MigrationScriptExecutor({ handler });

await executor.up();

2. Use environment variables for deployment:

# Set environment variables
export MSR_FOLDER=./database/migrations
export MSR_TABLE_NAME=migration_history
export MSR_LOGGING_ENABLED=true
export MSR_LOGGING_PATH=./logs

# Run your application
npm start

3. Override when needed:

// Env vars are loaded automatically, but you can override
const executor = new MigrationScriptExecutor({ handler }, {
    dryRun: true  // Override for this specific run
});

.env File Support (v0.7.0+)

MSR automatically loads environment variables from .env files using the config.envFileSources property. This is perfect for:

  • Local development - Keep credentials out of version control
  • Environment-specific configuration - Use .env.production, .env.development, etc.
  • Quick configuration - Simple key=value format without code changes
  • Team consistency - Share configuration templates via .env.example

Default Behavior

By default, MSR looks for these files (in priority order):

config.envFileSources = ['.env.local', '.env', 'env'];

Priority: First file takes precedence. If .env.local exists, values from .env are only used when not defined in .env.local.

Basic Example

Create a .env file in your project root:

# .env
MSR_FOLDER=./database/migrations
MSR_TABLE_NAME=migration_history
MSR_DRY_RUN=false
MSR_LOG_LEVEL=info

That’s it! MSR will automatically load these values:

const executor = new MigrationScriptExecutor({ handler });
// Configuration loaded from .env automatically

Environment-Specific Files

Development Setup

# .env.development
MSR_FOLDER=./migrations
MSR_LOG_LEVEL=debug
MSR_DRY_RUN=true
const config = new Config();
config.envFileSources = ['.env.development', '.env'];

Production Setup

# .env.production
MSR_FOLDER=/app/migrations
MSR_LOG_LEVEL=error
MSR_DRY_RUN=false
MSR_ROLLBACK_STRATEGY=BACKUP
const config = new Config();
config.envFileSources = ['.env.production', '.env'];

Local Overrides

Use .env.local for personal overrides (add to .gitignore):

# .env.local (not in version control)
MSR_FOLDER=./my-local-migrations
MSR_LOG_LEVEL=debug
// Default behavior - automatically uses .env.local if it exists
const config = new Config();
// config.envFileSources = ['.env.local', '.env', 'env'] (default)

Custom File Names

const config = new Config();
config.envFileSources = ['database.env', 'secrets.env'];

Disable .env Loading

To use only system environment variables:

const config = new Config();
config.envFileSources = []; // Disable .env file loading

File Format

.env files use simple KEY=VALUE format:

# Comments are supported
MSR_FOLDER=./migrations
MSR_TABLE_NAME=schema_version

# Quotes are optional for strings
MSR_LOG_LEVEL=info

# Booleans
MSR_DRY_RUN=false

# Numbers
MSR_DISPLAY_LIMIT=10

# Nested properties (dot notation)
MSR_BACKUP_FOLDER=./backups
MSR_BACKUP_DELETE_BACKUP=true
MSR_BACKUP_TIMESTAMP=true

Best Practices

  1. Version control - Commit .env.example with dummy values, ignore .env and .env.local
  2. Documentation - Document all env vars in .env.example
  3. Validation - Check required vars at startup
  4. Security - Never commit real credentials
  5. Consistency - Use same file names across environments
# .env.example (commit this)
MSR_FOLDER=./migrations
MSR_TABLE_NAME=schema_version
MSR_LOG_LEVEL=info
# Add your database credentials here
# .gitignore
.env
.env.local
.env.*.local

Environment Variables

Simple Properties

Configure basic settings with MSR_* environment variables:

# Migration folder
export MSR_FOLDER=./database/migrations

# Tracking table name
export MSR_TABLE_NAME=migration_history

# Before migrate hook name
export MSR_BEFORE_MIGRATE_NAME=beforeMigrate

# Display limit (0 = show all)
export MSR_DISPLAY_LIMIT=20

# Boolean flags
export MSR_DRY_RUN=true
export MSR_RECURSIVE=true
export MSR_VALIDATE_BEFORE_RUN=true
export MSR_STRICT_VALIDATION=false
export MSR_SHOW_BANNER=false

# Log level (error, warn, info, debug)
export MSR_LOG_LEVEL=info

Boolean values are case-insensitive:

  • true, 1, yes, ontrue
  • Everything else → false

Log levels (in order of verbosity):

  • error - Only errors (production)
  • warn - Warnings + errors
  • info - Normal operation (default)
  • debug - All logs including debug output

Complex Objects (Dot-Notation)

Configure nested objects using dot-notation (recommended):

Logging Configuration

export MSR_LOGGING_ENABLED=true
export MSR_LOGGING_PATH=./logs/migrations
export MSR_LOGGING_MAX_FILES=30
export MSR_LOGGING_TIMESTAMP_FORMAT=YYYY-MM-DD
export MSR_LOGGING_LOG_SUCCESSFUL=true

Backup Configuration

export MSR_BACKUP_FOLDER=./backups
export MSR_BACKUP_TIMESTAMP=true
export MSR_BACKUP_DELETE_BACKUP=true
export MSR_BACKUP_PREFIX=db-backup
export MSR_BACKUP_TIMESTAMP_FORMAT=YYYY-MM-DD-HH-mm-ss

Transaction Configuration

export MSR_TRANSACTION_MODE=PER_MIGRATION  # or PER_BATCH, NONE
export MSR_TRANSACTION_ISOLATION=READ_COMMITTED  # or READ_UNCOMMITTED, REPEATABLE_READ, SERIALIZABLE
export MSR_TRANSACTION_TIMEOUT=30000  # milliseconds
export MSR_TRANSACTION_RETRIES=3  # number of retry attempts
export MSR_TRANSACTION_RETRY_DELAY=100  # milliseconds
export MSR_TRANSACTION_RETRY_BACKOFF=true  # exponential backoff

Naming Convention:

  • MSR_ prefix for all variables
  • Nested properties use _ separator
  • camelCase property names converted to SNAKE_CASE
  • Example: config.logging.maxFilesMSR_LOGGING_MAX_FILES

Complex Objects (JSON)

Alternatively, use JSON for complex configuration:

# Logging as JSON
export MSR_LOGGING='{"enabled":true,"path":"./logs","maxFiles":30}'

# Backup as JSON
export MSR_BACKUP='{"folder":"./backups","timestamp":true,"deleteBackup":true}'

# Transaction as JSON
export MSR_TRANSACTION='{"mode":"PER_MIGRATION","isolation":"READ_COMMITTED","retries":3}'

Note: Dot-notation variables take precedence over JSON if both are set.


File Patterns

Configure migration file patterns using JSON array:

# Single pattern
export MSR_FILE_PATTERNS='["^V(\\d+)_.*\\.ts$"]'

# Multiple patterns (TypeScript and SQL)
export MSR_FILE_PATTERNS='["^V(\\d+)_.*\\.ts$","^V(\\d+)_.*\\.sql$"]'

Configuration Files

Create msr.config.js in your project root:

// msr.config.js
module.exports = {
    folder: './database/migrations',
    tableName: 'migration_history',
    displayLimit: 20,
    recursive: true,

    // Can use process.env for dynamic values
    validateBeforeRun: true,
    strictValidation: process.env.CI === 'true',

    logging: {
        enabled: true,
        path: './logs/migrations',
        maxFiles: 30,
        timestampFormat: 'YYYY-MM-DD'
    },

    backup: {
        folder: './backups',
        timestamp: true,
        deleteBackup: true,
        prefix: 'db-backup',
        timestampFormat: 'YYYY-MM-DD-HH-mm-ss'
    }
};

JSON Config

Create msr.config.json for static configuration:

{
    "folder": "./database/migrations",
    "tableName": "migration_history",
    "displayLimit": 20,
    "recursive": true,
    "validateBeforeRun": true,
    "strictValidation": false,
    "logging": {
        "enabled": true,
        "path": "./logs/migrations",
        "maxFiles": 30
    },
    "backup": {
        "folder": "./backups",
        "timestamp": true,
        "deleteBackup": true,
        "prefix": "db-backup"
    }
}

Custom Config File Location

Use MSR_CONFIG_FILE to specify a custom location:

export MSR_CONFIG_FILE=./config/production.config.js

Priority:

  1. MSR_CONFIG_FILE (if set)
  2. msr.config.js (if exists)
  3. msr.config.json (if exists)

Platform-Specific Examples

Docker

Dockerfile:

FROM node:18-alpine

WORKDIR /app

# Set environment variables
ENV MSR_FOLDER=/app/migrations
ENV MSR_TABLE_NAME=migration_history
ENV MSR_LOGGING_ENABLED=true
ENV MSR_LOGGING_PATH=/app/logs
ENV MSR_BACKUP_FOLDER=/app/backups

COPY package*.json ./
RUN npm ci --production

COPY . .

CMD ["npm", "start"]

docker-compose.yml:

version: '3.8'

services:
  app:
    build: .
    environment:
      - MSR_FOLDER=/app/migrations
      - MSR_TABLE_NAME=migration_history
      - MSR_LOGGING_ENABLED=true
      - MSR_LOGGING_PATH=/app/logs
      - MSR_BACKUP_FOLDER=/app/backups
      - MSR_BACKUP_TIMESTAMP=true
    volumes:
      - ./migrations:/app/migrations
      - ./logs:/app/logs
      - ./backups:/app/backups

Kubernetes

ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: msr-config
data:
  MSR_FOLDER: "/app/migrations"
  MSR_TABLE_NAME: "migration_history"
  MSR_LOGGING_ENABLED: "true"
  MSR_LOGGING_PATH: "/var/log/migrations"
  MSR_BACKUP_FOLDER: "/mnt/backups"
  MSR_BACKUP_TIMESTAMP: "true"
  MSR_VALIDATE_BEFORE_RUN: "true"

Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  template:
    spec:
      containers:
      - name: app
        image: myapp:latest
        envFrom:
        - configMapRef:
            name: msr-config
        volumeMounts:
        - name: migrations
          mountPath: /app/migrations
        - name: logs
          mountPath: /var/log/migrations
        - name: backups
          mountPath: /mnt/backups
      volumes:
      - name: migrations
        persistentVolumeClaim:
          claimName: migrations-pvc
      - name: logs
        persistentVolumeClaim:
          claimName: logs-pvc
      - name: backups
        persistentVolumeClaim:
          claimName: backups-pvc

GitHub Actions

.github/workflows/migrate.yml:

name: Run Migrations

on:
  push:
    branches: [main]

jobs:
  migrate:
    runs-on: ubuntu-latest

    env:
      MSR_FOLDER: ./migrations
      MSR_TABLE_NAME: migration_history
      MSR_STRICT_VALIDATION: true
      MSR_VALIDATE_BEFORE_RUN: true
      MSR_LOGGING_ENABLED: true
      MSR_DRY_RUN: false

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Run migrations
        run: npm run migrate
        env:
          DATABASE_URL: $

Cloud Platforms

AWS ECS Task Definition

{
  "containerDefinitions": [
    {
      "name": "app",
      "image": "myapp:latest",
      "environment": [
        {
          "name": "MSR_FOLDER",
          "value": "/app/migrations"
        },
        {
          "name": "MSR_TABLE_NAME",
          "value": "migration_history"
        },
        {
          "name": "MSR_LOGGING_ENABLED",
          "value": "true"
        },
        {
          "name": "MSR_BACKUP_FOLDER",
          "value": "/mnt/efs/backups"
        }
      ]
    }
  ]
}

Heroku

# Set config vars
heroku config:set MSR_FOLDER=./database/migrations
heroku config:set MSR_TABLE_NAME=migration_history
heroku config:set MSR_LOGGING_ENABLED=true
heroku config:set MSR_BACKUP_FOLDER=./backups

# Deploy
git push heroku main

Environment-Specific Strategies

Development (.env)

# .env.development
MSR_FOLDER=./migrations
MSR_DRY_RUN=false
MSR_STRICT_VALIDATION=false
MSR_LOGGING_ENABLED=true
MSR_BACKUP_DELETE_BACKUP=true

Usage with dotenv:

import 'dotenv/config';
import { MigrationScriptExecutor } from '@migration-script-runner/core';

// Environment variables loaded automatically
const executor = new MigrationScriptExecutor({ handler });
await executor.up();

Staging

# .env.staging
MSR_FOLDER=./database/migrations
MSR_TABLE_NAME=migration_history
MSR_VALIDATE_BEFORE_RUN=true
MSR_STRICT_VALIDATION=false
MSR_LOGGING_ENABLED=true
MSR_LOGGING_PATH=/var/log/migrations
MSR_BACKUP_FOLDER=/var/backups
MSR_BACKUP_TIMESTAMP=true

Production

# .env.production
MSR_FOLDER=/app/migrations
MSR_TABLE_NAME=migration_history
MSR_VALIDATE_BEFORE_RUN=true
MSR_STRICT_VALIDATION=false
MSR_LOGGING_ENABLED=true
MSR_LOGGING_PATH=/var/log/migrations
MSR_BACKUP_FOLDER=/var/backups/database
MSR_BACKUP_TIMESTAMP=true
MSR_BACKUP_DELETE_BACKUP=true

Advanced Configuration

Manual Loading with ConfigLoader

For advanced use cases, use ConfigLoader directly:

import { ConfigLoader, MigrationScriptExecutor } from '@migration-script-runner/core';

// Load with waterfall approach
const config = ConfigLoader.load();

// Load with overrides (highest priority)
const config = ConfigLoader.load({
    folder: './migrations',
    dryRun: true
});

// Load from specific directory
const config = ConfigLoader.load({}, '/app');

const executor = new MigrationScriptExecutor({ handler , config });

See ConfigLoader API Reference for detailed documentation.


Adapter Extensibility with Automatic Parsing

New in v0.7.0: Database adapters can extend ConfigLoader to add custom environment variables with automatic parsing.

Why Use Automatic Parsing?

When building database adapters, you typically need to support additional environment variables (like POSTGRES_HOST, MYSQL_PORT, etc.). The automatic parsing feature eliminates manual mapping.

Before (Manual Mapping)

class PostgresConfigLoader extends ConfigLoader<PostgresConfig> {
    applyEnvironmentVariables(config: PostgresConfig): void {
        super.applyEnvironmentVariables(config); // MSR_* vars

        // ❌ Manual mapping - error-prone, requires updates
        if (process.env.POSTGRES_HOST) {
            config.host = process.env.POSTGRES_HOST;
        }
        if (process.env.POSTGRES_PORT) {
            config.port = parseInt(process.env.POSTGRES_PORT);
        }
        if (process.env.POSTGRES_SSL) {
            config.ssl = process.env.POSTGRES_SSL === 'true';
        }
        if (process.env.POSTGRES_POOL_SIZE) {
            config.poolSize = parseInt(process.env.POSTGRES_POOL_SIZE);
        }
        // ... 20 more properties to map manually
    }
}

After (Automatic Parsing)

class PostgresConfigLoader extends ConfigLoader<PostgresConfig> {
    applyEnvironmentVariables(config: PostgresConfig): void {
        super.applyEnvironmentVariables(config); // MSR_* vars

        // ✅ Automatic parsing - zero manual mapping!
        this.autoApplyEnvironmentVariables(config, 'POSTGRES');
    }
}

// Config class with typed properties
class PostgresConfig extends Config {
    host: string = 'localhost';
    port: number = 5432;
    ssl: boolean = false;
    poolSize: number = 10;
}

How It Works

  1. Reflection-based Discovery: Automatically finds all config properties
  2. Naming Convention: Converts camelCaseSNAKE_CASE
    • hostPOSTGRES_HOST
    • poolSizePOSTGRES_POOL_SIZE
  3. Type Coercion: Uses default value types for automatic conversion
    • host: stringprocess.env.POSTGRES_HOST as string
    • port: numberparseInt(process.env.POSTGRES_PORT)
    • ssl: booleanparseBoolean(process.env.POSTGRES_SSL)

Supported Types

  • Primitives: string, number, boolean
  • Arrays: JSON parsing (e.g., ["pattern1", "pattern2"])
  • Nested Objects: Dot-notation (e.g., POSTGRES_POOL_CONFIG_MIN)
  • Complex Objects: Recursive parsing

Example with Nested Objects

class PostgresConfig extends Config {
    poolConfig: {
        min: number;
        max: number;
        idleTimeout: number;
    } = {
        min: 2,
        max: 10,
        idleTimeout: 30000
    };
}

// Environment variables (automatically mapped):
// POSTGRES_POOL_CONFIG_MIN=5
// POSTGRES_POOL_CONFIG_MAX=20
// POSTGRES_POOL_CONFIG_IDLE_TIMEOUT=60000

Custom Overrides for Special Cases

For properties requiring validation or special handling:

class PostgresConfigLoader extends ConfigLoader<PostgresConfig> {
    applyEnvironmentVariables(config: PostgresConfig): void {
        super.applyEnvironmentVariables(config);

        const overrides = new Map();

        // Custom validation for port
        overrides.set('port', (cfg: PostgresConfig, envVar: string) => {
            const value = process.env[envVar];
            if (value) {
                const port = parseInt(value, 10);
                if (port >= 1 && port <= 65535) {
                    cfg.port = port;
                } else {
                    console.warn(`Invalid port ${port}, using default ${cfg.port}`);
                }
            }
        });

        this.autoApplyEnvironmentVariables(config, 'POSTGRES', overrides);
    }
}

Benefits

Zero Manual Mapping: New properties automatically get env var support ✅ Type Safe: Uses TypeScript types for automatic coercion ✅ Consistent Naming: Automatic camelCaseSNAKE_CASEMaintainable: No updates needed when adding properties ✅ Extensible: Override system for special cases

Complete Adapter Example

import { ConfigLoader, Config } from '@migration-script-runner/core';

// 1. Define adapter config
class PostgresConfig extends Config {
    host: string = 'localhost';
    port: number = 5432;
    database: string = 'mydb';
    user: string = 'postgres';
    password: string = '';
    ssl: boolean = false;
    poolSize: number = 10;
    connectionTimeout: number = 5000;
}

// 2. Create adapter config loader
class PostgresConfigLoader extends ConfigLoader<PostgresConfig> {
    applyEnvironmentVariables(config: PostgresConfig): void {
        // Apply MSR_* vars
        super.applyEnvironmentVariables(config);

        // Automatically apply POSTGRES_* vars
        this.autoApplyEnvironmentVariables(config, 'POSTGRES');
    }
}

// 3. Use in your adapter
const configLoader = new PostgresConfigLoader();
const config = configLoader.load();

// Environment variables supported automatically:
// POSTGRES_HOST=db.example.com
// POSTGRES_PORT=5432
// POSTGRES_DATABASE=production_db
// POSTGRES_USER=app_user
// POSTGRES_PASSWORD=secret
// POSTGRES_SSL=true
// POSTGRES_POOL_SIZE=20
// POSTGRES_CONNECTION_TIMEOUT=10000

See ConfigLoader API Reference for complete documentation.


Using auto-envparse Directly

New in v0.7.0: MSR uses the auto-envparse library for automatic environment variable parsing. This library was extracted from MSR and is available as a standalone npm package.

Why Use auto-envparse?

  • Framework-agnostic: Use outside of MSR context
  • Reusable: Parse env vars for any configuration object
  • No inheritance required: Works with plain objects
  • Zero dependencies: Lightweight and fast
  • 12-Factor App compliant: Best practices for configuration

Package: npm install auto-envparse Repository: https://github.com/vlavrynovych/auto-envparse

Basic Usage

import AutoEnvParse from 'auto-envparse';

// Your configuration object (can be any object)
const dbConfig = {
    host: 'localhost',
    port: 5432,
    database: 'mydb',
    ssl: false,
    poolSize: 10
};

// Environment variables:
// DB_HOST=prod.example.com
// DB_PORT=5433
// DB_SSL=true
// DB_POOL_SIZE=20

// Parse environment variables automatically
AutoEnvParse.parse(dbConfig, { prefix: 'DB' });

// Result:
console.log(dbConfig.host);     // 'prod.example.com'
console.log(dbConfig.port);     // 5433 (number)
console.log(dbConfig.ssl);      // true (boolean)
console.log(dbConfig.poolSize); // 20 (number)

Features

  • Automatic type detection: Infers types from default values
  • Naming convention: Converts camelCaseSNAKE_CASE
  • Nested objects: Supports dot-notation (DB_POOL_MIN, DB_POOL_MAX)
  • Nested arrays (v2.1+): Supports array indexing (APP_SERVERS_0_HOST, APP_SERVERS_1_HOST)
  • Type coercion: Automatically converts strings to correct types
  • Custom overrides: Add validation or special handling
  • Transform functions (v2.1+): Custom transformations before assignment
  • .env file loading (v2.1+): Multi-source .env file support

Example with Nested Objects

const appConfig = {
    port: 3000,
    cors: {
        enabled: true,
        origin: '*'
    },
    rateLimit: {
        windowMs: 900000,
        max: 100
    }
};

// Environment:
// APP_PORT=8080
// APP_CORS_ENABLED=false
// APP_CORS_ORIGIN=https://example.com
// APP_RATE_LIMIT_MAX=1000

AutoEnvParse.parse(appConfig, { prefix: 'APP' });

Example with Custom Validation

import AutoEnvParse from 'auto-envparse';

const config = {
    port: 3000,
    environment: 'development'
};

// Add custom validation for port and environment
const overrides = new Map();
overrides.set('port', (obj, envVar) => {
    const value = process.env[envVar];
    if (value) {
        const port = parseInt(value, 10);
        if (port >= 1 && port <= 65535) {
            obj.port = port;
        } else {
            console.warn(`Invalid port: ${port}, using default`);
        }
    }
});

// Use AutoEnvParse enum validator
overrides.set('environment', AutoEnvParse.enumValidator('environment',
    ['development', 'staging', 'production'],
    { caseSensitive: false }
));

// APP_PORT=8080 APP_ENVIRONMENT=production
AutoEnvParse.parse(config, { prefix: 'APP', overrides });
console.log(config.port);        // 8080
console.log(config.environment); // 'production'

Advanced Features (v2.1+)

Transform Functions:

import AutoEnvParse from 'auto-envparse';

const config = {
    timeout: AutoEnvParse.transform(30, (val) => val * 1000), // Convert seconds to ms
    maxRetries: 3
};

// Environment: APP_TIMEOUT=60 APP_MAX_RETRIES=5
AutoEnvParse.parse(config, { prefix: 'APP' });
console.log(config.timeout);     // 60000 (60 seconds * 1000)
console.log(config.maxRetries);  // 5

.env File Loading:

import AutoEnvParse from 'auto-envparse';

const config = {
    host: 'localhost',
    port: 5432
};

// Auto-loads from .env, .env.local, etc.
AutoEnvParse.parse(config, {
    prefix: 'DB',
    sources: ['.env.local', '.env', 'env'] // Priority order
});

Use Cases

  1. Microservices: Parse service-specific configuration
  2. CLI tools: Load tool configuration from environment
  3. Testing: Mock configuration with environment variables
  4. Libraries: Provide env var configuration without dependencies
  5. Any Node.js project: Zero-config environment variable parsing

MSR Integration

MSR’s ConfigLoader internally uses auto-envparse for environment variable parsing. When you extend ConfigLoader for adapters, you’re using the same battle-tested parsing logic that’s available as a standalone package.


Validation

Ensure required environment variables are set:

import { ConfigLoader } from '@migration-script-runner/core';

// Validate required variables
ConfigLoader.validateRequired([
    'DATABASE_URL',
    'MSR_FOLDER',
    'MSR_TABLE_NAME'
]);

// Throws error with list of missing variables if any are not set

Troubleshooting

Environment Variables Not Applied

Problem: Environment variables don’t seem to affect configuration.

Solutions:

  1. Check variable names - Must start with MSR_
  2. Check spelling - camelCase → SNAKE_CASE (e.g., maxFilesMAX_FILES)
  3. Restart application - Environment variables are loaded at startup
  4. Check priority - Constructor overrides take precedence over env vars

Debug:

console.log('MSR_FOLDER:', process.env.MSR_FOLDER);
const config = ConfigLoader.load();
console.log('Loaded folder:', config.folder);

Config File Not Found

Problem: Warning: “MSR_CONFIG_FILE points to non-existent file”

Solutions:

  1. Check file path is relative to process.cwd() or absolute
  2. Verify file exists: ls msr.config.js
  3. Check MSR_CONFIG_FILE value: echo $MSR_CONFIG_FILE

Invalid JSON Format

Problem: Warning: “Invalid MSR_LOGGING JSON” or “Invalid MSR_FILE_PATTERNS format”

Solutions:

  1. Validate JSON syntax: Use a JSON validator
  2. Escape properly in shell: Use single quotes '{"key":"value"}'
  3. Use dot-notation instead (recommended)

Instead of:

export MSR_LOGGING='{"enabled":true}'  # Can be error-prone

Use:

export MSR_LOGGING_ENABLED=true  # Cleaner, less error-prone

Complete Example

Production-ready configuration combining all approaches:

msr.config.js:

module.exports = {
    folder: process.env.MSR_FOLDER || './migrations',
    tableName: process.env.MSR_TABLE_NAME || 'schema_version',
    validateBeforeRun: true,
    strictValidation: process.env.NODE_ENV === 'production',

    logging: {
        enabled: true,
        path: process.env.MSR_LOGGING_PATH || './logs'
    },

    backup: {
        folder: process.env.MSR_BACKUP_FOLDER || './backups',
        timestamp: true,
        deleteBackup: process.env.NODE_ENV === 'production'
    }
};

Environment variables (.env.production):

NODE_ENV=production
MSR_FOLDER=/app/migrations
MSR_TABLE_NAME=migration_history
MSR_LOGGING_PATH=/var/log/migrations
MSR_BACKUP_FOLDER=/var/backups/database

Application code:

import { MigrationScriptExecutor } from '@migration-script-runner/core';
import { DatabaseHandler } from './database';

const handler = new DatabaseHandler();

// Automatically loads: defaults → file → env vars
const executor = new MigrationScriptExecutor({ handler });

await executor.migrate();

Reference


Next Steps