Configuration Files — Complete Implementation Guide
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
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.
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.
Not validating config file content -- Invalid or missing configuration should fail at startup. Use a schema validator (JSON Schema, Joi, Zod).
Mixing config file formats -- Using JSON in one service and YAML in another creates cognitive load. Standardize on one format per project.
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
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.
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.
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.
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
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