Skip to content

Configuration Files — Complete Implementation Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Configuration Files. We cover key concepts, practical examples, and best practices to help you master this topic.

Configuration files store application settings in structured formats like JSON, YAML, and TOML, providing richer data structures than flat environment variables while keeping configuration separate from code.

What You'll Learn

By the end of this tutorial, you will understand the strengths and weaknesses of each configuration file format, how to load and validate them, and when to use config files vs environment variables.

Why It Matters

Configuration files support complex data structures (nested objects, arrays) that are difficult to represent in environment variables. They're the right choice for configuration that doesn't change between deploys.

Real-World Use

DodaTech's Microservices use YAML for infrastructure configuration (database connections, cache settings) and environment variables for deployment-specific values (hostnames, passwords).

Configuration Files Learning Path

flowchart LR
  A[Dotenv] --> B[Configuration Files]
  B --> C[JSON]
  B --> D[YAML]
  B --> E[TOML]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

JSON Configuration Files

JSON is the simplest and most widely supported config file format.

// config.json
{
  "server": {
    "port": 3000,
    "host": "0.0.0.0"
  },
  "database": {
    "host": "localhost",
    "port": 5432,
    "name": "myapp",
    "pool": {
      "min": 2,
      "max": 10
    }
  },
  "logging": {
    "level": "info",
    "format": "json",
    "outputs": ["console", "file"]
  },
  "features": {
    "enableCache": true,
    "enableMetrics": true,
    "maintenanceMode": false
  }
}
const fs = require("fs");
const path = require("path");

function loadJSONConfig(filePath) {
  try {
    const raw = fs.readFileSync(path.resolve(filePath), "utf8");
    const config = JSON.parse(raw);
    console.log("Loaded JSON config from", filePath);
    return config;
  } catch (err) {
    if (err.code === "ENOENT") {
      console.warn("Config file not found:", filePath);
      return {};
    }
    throw new Error(`Invalid JSON in ${filePath}: ${err.message}`);
  }
}

const config = loadJSONConfig("./config.json");
console.log("Server port:", config.server.port);
console.log("Database pool:", config.database.pool);
// Server port: 3000
// Database pool: { min: 2, max: 10 }

YAML Configuration Files

YAML is the most popular format for complex configuration due to its readability.

# config.yaml
server:
  port: 3000
  host: 0.0.0.0

database:
  host: localhost
  port: 5432
  name: myapp
  pool:
    min: 2
    max: 10

logging:
  level: info
  format: json
  outputs:
    - console
    - file

features:
  enableCache: true
  enableMetrics: true
  maintenanceMode: false
const fs = require("fs");
const yaml = require("js-yaml");

function loadYAMLConfig(filePath) {
  try {
    const raw = fs.readFileSync(filePath, "utf8");
    const config = yaml.load(raw);
    console.log("Loaded YAML config from", filePath);
    return config;
  } catch (err) {
    if (err.code === "ENOENT") {
      console.warn("Config file not found:", filePath);
      return {};
    }
    throw new Error(`Invalid YAML in ${filePath}: ${err.message}`);
  }
}

const config = loadYAMLConfig("./config.yaml");
console.log("Log level:", config.logging.level);
console.log("Enabled features:", Object.keys(config.features).filter(k => config.features[k]));
// Log level: info
// Enabled features: ['enableCache', 'enableMetrics']

TOML Configuration Files

TOML is designed for configuration files with unambiguous syntax.

# config.toml
[server]
port = 3000
host = "0.0.0.0"

[database]
host = "localhost"
port = 5432
name = "myapp"

[database.pool]
min = 2
max = 10

[logging]
level = "info"
format = "json"
outputs = ["console", "file"]

[features]
enableCache = true
enableMetrics = true
maintenanceMode = false
const fs = require("fs");
const toml = require("@iarna/toml");

function loadTOMLConfig(filePath) {
  try {
    const raw = fs.readFileSync(filePath, "utf8");
    const config = toml.parse(raw);
    console.log("Loaded TOML config from", filePath);
    return config;
  } catch (err) {
    if (err.code === "ENOENT") {
      console.warn("Config file not found:", filePath);
      return {};
    }
    throw new Error(`Invalid TOML in ${filePath}: ${err.message}`);
  }
}

const config = loadTOMLConfig("./config.toml");
console.log("Server:", JSON.stringify(config.server));
console.log("Features:", config.features);
// Server: {"port":3000,"host":"0.0.0.0"}
// Features: { enableCache: true, enableMetrics: true, maintenanceMode: false }

Environment-Specific Config Files

Load different files based on the environment.

function loadEnvironmentConfig() {
  const env = process.env.NODE_ENV || "development";
  const fs = require("fs");
  const yaml = require("js-yaml");
  const path = require("path");

  // Base config loaded first
  const basePath = path.join(__dirname, "config", "default.yaml");
  let config = {};
  if (fs.existsSync(basePath)) {
    config = yaml.load(fs.readFileSync(basePath, "utf8"));
    console.log("Loaded base config");
  }

  // Environment-specific overrides
  const envPath = path.join(__dirname, "config", `${env}.yaml`);
  if (fs.existsSync(envPath)) {
    const overrides = yaml.load(fs.readFileSync(envPath, "utf8"));
    config = deepMerge(config, overrides);
    console.log(`Loaded ${env} overrides`);
  }

  return config;
}

function deepMerge(base, overrides) {
  const result = { ...base };
  for (const [key, value] of Object.entries(overrides)) {
    if (value && typeof value === "object" && !Array.isArray(value)) {
      result[key] = deepMerge(result[key] || {}, value);
    } else {
      result[key] = value;
    }
  }
  return result;
}

// config/default.yaml:
// database:
//   host: localhost
//   port: 5432
//   pool:
//     max: 5

// config/production.yaml:
// database:
//   host: prod.database.com
//   pool:
//     max: 25

// Result for production:
// database: { host: prod.database.com, port: 5432, pool: { max: 25 } }

Common Mistakes

  1. Committing secrets in config files -- Config files in version control should use placeholders or references to secrets. Store actual secrets in environment variables or secrets manager.

  2. Using the wrong format for the use case -- JSON is good for simple config, YAML for complex config, TOML for application config. Choose based on your needs.

  3. Not validating config file content -- Invalid or missing configuration should fail at startup. Use a schema validator (JSON Schema, Joi, Zod).

  4. Mixing config file formats -- Using JSON in one service and YAML in another creates cognitive load. Standardize on one format per project.

  5. Loading config files synchronously in async contexts -- In production startup code, synchronous file reads are fine (they happen once). In Serverless or hot-reload scenarios, use async loading.

Practice Questions

  1. What is the main advantage of YAML over JSON for configuration files? YAML supports comments, is more readable for complex nested structures, and has a cleaner syntax without curly braces and quotes.

  2. When should you use environment variables instead of config files? For deployment-specific values (hostnames, ports, secrets) and values that change between environments without code changes.

  3. How do you handle environment-specific overrides in config files? Load a base config file first, then apply environment-specific override files that deep-merge with the base.

  4. Challenge: Implement a config file loader that auto-detects the format based on file extension.

function autoDetectConfigLoader(filePath) {
  const fs = require("fs");
  const path = require("path");
  const ext = path.extname(filePath).toLowerCase();

  const loaders = {
    ".json": (f) => JSON.parse(fs.readFileSync(f, "utf8")),
    ".yaml": (f) => require("js-yaml").load(fs.readFileSync(f, "utf8")),
    ".yml": (f) => require("js-yaml").load(fs.readFileSync(f, "utf8")),
    ".toml": (f) => require("@iarna/toml").parse(fs.readFileSync(f, "utf8"))
  };

  const loader = loaders[ext];
  if (!loader) {
    throw new Error(`Unsupported config file format: ${ext}`);
  }

  return loader(filePath);
}

const config = autoDetectConfigLoader("./config.yaml");
console.log("Auto-loaded config format: YAML");

FAQ

Which config file format is best for production?

YAML is the most popular for production due to its readability and support for comments. JSON is simpler but lacks comments.

Should I use one large config file or multiple smaller ones?

Multiple smaller files organized by concern (database.yaml, cache.yaml, logging.yaml) are easier to manage than one large file.

Can I use environment variable substitution in config files?

Yes, with custom loading logic. Read the config file, resolve ${VAR} patterns, and replace with environment variable values before parsing.

How do I validate configuration at startup?

Use a schema validation library. Define the expected shape and types, and validate the loaded config before the application starts serving.

What is the recommended config file directory structure?

config/default.yaml, config/development.yaml, config/production.yaml, config/staging.yaml. Load defaults first, then environment-specific overrides.

Mini Project

Build a multi-format configuration loader that detects file format, supports environment-specific overrides through deep merging, validates with a schema, and provides clear error messages.

class ConfigManager {
  constructor(configDir = "./config") {
    this.configDir = configDir;
  }

  load() {
    const env = process.env.NODE_ENV || "development";
    const path = require("path");

    const defaultConfig = this.loadFile(path.join(this.configDir, "default.yaml"));
    const envConfig = this.loadFile(path.join(this.configDir, `${env}.yaml`));

    return this.deepMerge(defaultConfig, envConfig);
  }

  loadFile(filePath) {
    try {
      return require("js-yaml").load(require("fs").readFileSync(filePath, "utf8"));
    } catch {
      return {};
    }
  }

  deepMerge(base, override) {
    const result = { ...base };
    for (const [key, value] of Object.entries(override || {})) {
      if (value && typeof value === "object" && !Array.isArray(value)) {
        result[key] = this.deepMerge(result[key] || {}, value);
      } else {
        result[key] = value;
      }
    }
    return result;
  }
}

const mgr = new ConfigManager();
const config = mgr.load();
console.log("Configuration ready");

What's Next

Now that you understand config file formats, dive deeper into YAML configuration. Then explore TOML configuration.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro