favour-config-loader
v1.0.0
Published
Type-safe configuration loader for backend systems.
Readme
Config Loader
A type-safe configuration loader for Node.js backend systems. Load, validate, and type-check environment variables at runtime with full TypeScript support.
Why Config Loader?
Environment variables are strings. Without validation, you risk crashes at runtime:
// Without validation
const port = process.env.PORT; // Could be "abc", could be empty
app.listen(port); // Crashes if invalid
// With Config Loader
const config = loadConfig(schema); // Validates immediately
if (config.status === "success") {
app.listen(config.data.port); // Safe, type-checked
}Config Loader catches configuration errors before they reach production.
Installation
npm install @favour/config-loaderQuick Start
1. Define Your Configuration Type
type AppConfig = {
port: number;
databaseUrl: string;
apiKey: string;
environment: "production" | "development";
};2. Create a Schema
import { Schema } from "@favour/config-loader";
const appConfigSchema: Schema<AppConfig> = {
port: {
type: "number",
required: true,
validate: (value) => value > 0 && value < 65536,
},
databaseUrl: {
type: "string",
required: true,
validate: (value) => value.includes("://"),
},
apiKey: {
type: "string",
required: true,
validate: (value) => value.length >= 10,
},
environment: {
type: "string",
required: true,
},
};3. Load Configuration
import { loadConfig } from "@favour/config-loader";
const result = loadConfig(appConfigSchema);
if (result.status === "success") {
console.log("Port:", result.data.port);
console.log("Database:", result.data.databaseUrl);
} else {
console.error("Config error:", result.message);
process.exit(1);
}4. Set Environment Variables
Create a .env file:
PORT=3000
DATABASE_URL=postgresql://localhost:5432/mydb
API_KEY=your_secret_key_here
ENVIRONMENT=developmentLoad it with dotenv:
import dotenv from "dotenv";
dotenv.config();
const config = loadConfig(appConfigSchema);API Reference
Schema Type
type Schema<T> = {
[K in keyof T]: {
type: "string" | "number" | "boolean";
required: boolean;
validate?: (value: T[K]) => boolean;
};
};Each field in your schema defines:
type: The expected data type ("string", "number", "boolean")required: Whether the field must be presentvalidate: Optional validation function that returns true if valid
loadConfig Function
function loadConfig<T>(schema: Schema<T>): Result<T>;Loads environment variables and validates them against the schema.
Returns: A Result<T> which is either:
{ status: "success"; data: T }- Configuration loaded and valid{ status: "error"; message: string }- Configuration invalid or missing
Result Type
type Result<T> =
| { status: "success"; data: T }
| { status: "error"; message: string };validateConfig Function
function validateConfig<T>(config: T, schema: Schema<T>): T;Manually validate a configuration object against a schema. Throws an error if validation fails.
Usage Examples
Basic Configuration
import { loadConfig, Schema } from "@favour/config-loader";
type Config = {
appName: string;
port: number;
};
const schema: Schema<Config> = {
appName: {
type: "string",
required: true,
},
port: {
type: "number",
required: true,
validate: (value) => value > 1024,
},
};
const result = loadConfig(schema);
if (result.status === "success") {
console.log(`${result.data.appName} running on port ${result.data.port}`);
}With Optional Fields
type Config = {
requiredField: string;
optionalField: string;
};
const schema: Schema<Config> = {
requiredField: {
type: "string",
required: true,
},
optionalField: {
type: "string",
required: false,
},
};Multiple Validators
const schema: Schema<AppConfig> = {
apiKey: {
type: "string",
required: true,
validate: (value) => {
// Multiple checks
return value.length >= 10 && value.includes("-");
},
},
};Database Configuration
type DatabaseConfig = {
host: string;
port: number;
username: string;
password: string;
database: string;
};
const schema: Schema<DatabaseConfig> = {
host: {
type: "string",
required: true,
validate: (value) => value.length > 0,
},
port: {
type: "number",
required: true,
validate: (value) => value > 0 && value < 65536,
},
username: {
type: "string",
required: true,
},
password: {
type: "string",
required: true,
validate: (value) => value.length >= 8,
},
database: {
type: "string",
required: true,
},
};Error Handling
Config Loader never crashes silently. It returns errors explicitly:
const result = loadConfig(schema);
if (result.status === "error") {
// Handle the error
console.error("Configuration failed:", result.message);
// Exit process
process.exit(1);
}
// Type-safe access to data
const config = result.data; // TypeScript knows this is your config typeHow Config Loader Works
- Reads environment variables from
process.env - Converts variable names (camelCase to UPPER_SNAKE_CASE):
databaseUrl→DATABASE_URLapiKey→API_KEYport→PORT
- Converts values to correct types (strings to numbers, etc.)
- Runs validation functions
- Returns result or error
Testing
Run tests:
npm testWatch mode (re-run on file changes):
npm run test:watchReal-World Patterns
Pattern 1: Separate Config by Environment
// config/app.ts
const appConfigSchema: Schema<AppConfig> = { ... };
// config/database.ts
const dbConfigSchema: Schema<DatabaseConfig> = { ... };
// config/index.ts
export function initializeConfig() {
const appConfig = loadConfig(appConfigSchema);
const dbConfig = loadConfig(dbConfigSchema);
if (appConfig.status === "error" || dbConfig.status === "error") {
throw new Error("Configuration failed");
}
return {
app: appConfig.data,
db: dbConfig.data,
};
}Pattern 2: Default Values
const schema: Schema<Config> = {
environment: {
type: "string",
required: false, // Optional
},
};
const result = loadConfig(schema);
const environment =
result.status === "success" ? result.data.environment : "development"; // DefaultPattern 3: Nested Configuration
For complex configurations, create separate types and loaders:
type ServerConfig = {
port: number;
host: string;
};
type AuthConfig = {
jwtSecret: string;
tokenExpiry: number;
};
type FullConfig = {
server: ServerConfig;
auth: AuthConfig;
};
// Load separately then combine
const serverResult = loadConfig(serverSchema);
const authResult = loadConfig(authSchema);Best Practices
- Define schemas close to where they're used
- Validate early, at application startup
- Use specific validation rules (length, format, range)
- Fail fast - exit if configuration is invalid
- Document required environment variables in
.env.example - Never log sensitive values (passwords, API keys)
Environment Variables Example
Create .env.example for documentation:
# Server Configuration
PORT=3000
ENVIRONMENT=development
# Database
DATABASE_URL=postgresql://localhost:5432/mydb
# Security
API_KEY=your_api_key_here
JWT_SECRET=your_jwt_secret_hereThen create .env for actual values and add .env to .gitignore.
License
MIT
Contributing
Contributions welcome. Please write tests for new features.
npm testTroubleshooting
"Is required" error
Make sure the environment variable is set:
export DATABASE_URL=postgresql://...
npm startOr in .env:
DATABASE_URL=postgresql://..."Validation failed" error
Check your validation function. For example, if port validation is:
validate: (value) => value > 0;Make sure the value is actually greater than 0. Add logging to debug:
validate: (value) => {
console.log("Validating port:", value);
return value > 0;
};Type errors in TypeScript
Make sure your config type matches your schema:
type Config = {
port: number;
};
// Schema must have all fields from Config
const schema: Schema<Config> = {
port: { ... }
// Missing: other fields?
};