@filipgorny/config
v0.0.1
Published
Type-safe configuration management library with built-in validation and dependency injection support
Downloads
10
Maintainers
Readme
@filipgorny/config
Type-safe configuration management library with built-in validation and dependency injection support.
Features
- 🔒 Type-safe - Full TypeScript support with type inference
- ✅ Automatic Validation - Validates required properties at runtime
- 🎯 Simple API - Clean and intuitive interface
- 🔄 Default Values - Built-in support for default configurations
- 🪶 Lightweight - Minimal dependencies
- 🔌 DI Support - Works seamlessly with dependency injection containers
- 📄 DotEnv Integration - Built-in support for .env files
- 🚀 Zero Config - Works out of the box
Installation
npm install @filipgorny/configor
pnpm add @filipgorny/configor
yarn add @filipgorny/configQuick Start
import { createDotEnvConfig } from "@filipgorny/config";
const config = createDotEnvConfig();
// Read configuration with validation
const appConfig = config.read({
PORT: 3000, // Default value
NODE_ENV: "development", // Default value
API_KEY: undefined, // Required (throws if missing)
DATABASE_URL: undefined, // Required (throws if missing)
});
console.log(appConfig.PORT); // number
console.log(appConfig.API_KEY); // string
console.log(appConfig.DATABASE_URL); // stringUsage
Basic Configuration Reading
The read() method is the recommended way to read configuration values. It provides:
- Automatic validation for required properties
- Default values for optional properties
- Type-safe return values
import { createDotEnvConfig } from "@filipgorny/config";
const config = createDotEnvConfig();
// Define your configuration schema
const appConfig = config.read({
// Required properties (undefined = must exist)
DATABASE_URL: undefined,
API_KEY: undefined,
// Optional properties with defaults
PORT: 3000,
NODE_ENV: "development",
LOG_LEVEL: "info",
ENABLE_DEBUG: false,
});
// All properties are now guaranteed to exist
console.log(appConfig.DATABASE_URL); // string - guaranteed to exist
console.log(appConfig.PORT); // number - from env or default
console.log(appConfig.NODE_ENV); // string - from env or defaultError Handling
When a required property is missing, a ConfigPropertyDoesNotExistError is thrown:
import {
createDotEnvConfig,
ConfigPropertyDoesNotExistError,
} from "@filipgorny/config";
const config = createDotEnvConfig();
try {
const appConfig = config.read({
REQUIRED_API_KEY: undefined,
});
} catch (error) {
if (error instanceof ConfigPropertyDoesNotExistError) {
console.error(`Missing config: ${error.propertyName}`);
// Output: Missing config: REQUIRED_API_KEY
process.exit(1);
}
}Individual Property Access
You can also access properties individually using get() and has():
const config = createDotEnvConfig();
// Check if property exists
if (config.has("API_KEY")) {
const apiKey = config.get<string>("API_KEY");
console.log(apiKey);
}
// Get with type
const port = config.get<number>("PORT");
const debug = config.get<boolean>("DEBUG");Custom .env File Path
By default, the library searches for .env file starting from the current directory and moving upward. You can specify a custom path:
import { createDotEnvConfig } from "@filipgorny/config";
// Absolute path
const config1 = createDotEnvConfig("/path/to/.env");
// Relative path
const config2 = createDotEnvConfig("./config/.env");
// Different environment files
const config3 = createDotEnvConfig(".env.production");Organizing Configuration
Recommended Pattern:
Create a centralized configuration file for your application:
// src/config/index.ts
import { createDotEnvConfig } from "@filipgorny/config";
const configProvider = createDotEnvConfig();
export const config = configProvider.read({
// Server
PORT: 3000,
HOST: "localhost",
NODE_ENV: "development",
// Database
DATABASE_URL: undefined, // Required
DATABASE_POOL_SIZE: 10,
// External APIs
OPENAI_API_KEY: undefined, // Required
STRIPE_SECRET_KEY: undefined, // Required
// Features
ENABLE_CACHE: true,
ENABLE_RATE_LIMITING: true,
// Logging
LOG_LEVEL: "info",
});
export type AppConfig = typeof config;Then import it throughout your application:
// src/server.ts
import { config } from "./config";
const server = createServer({
port: config.PORT,
host: config.HOST,
});
// src/database.ts
import { config } from "./config";
const db = connectDatabase(config.DATABASE_URL);API Reference
createDotEnvConfig(dotenvPath?: string): Config
Creates a Config instance with DotEnv provider.
Parameters:
dotenvPath(optional): Path to .env file. Defaults to.envand searches upward from current directory.
Returns: Config instance
Example:
const config = createDotEnvConfig();
const config = createDotEnvConfig(".env.production");
const config = createDotEnvConfig("/absolute/path/.env");config.read<T>(properties: T): ConfigValues<T>
Reads multiple configuration properties with automatic validation.
Parameters:
properties: Object where keys are property names and values are default valuesundefined= required property (throws if missing)- Any other value = default value if property doesn't exist
Returns: Object with all requested properties
Throws: ConfigPropertyDoesNotExistError if a required property is missing
Example:
const values = config.read({
REQUIRED_PROP: undefined, // Must exist
OPTIONAL_PROP: "default", // Uses default if missing
});config.get<T>(key: string): T
Gets a single configuration value.
Parameters:
key: Configuration key name
Returns: Configuration value or undefined if not found
Example:
const apiKey = config.get<string>("API_KEY");
const port = config.get<number>("PORT");
const debug = config.get<boolean>("DEBUG");config.has(key: string): boolean
Checks if a configuration key exists.
Parameters:
key: Configuration key name
Returns: true if key exists, false otherwise
Example:
if (config.has("OPTIONAL_FEATURE")) {
enableFeature();
}ConfigPropertyDoesNotExistError
Error thrown when a required configuration property is missing.
Properties:
propertyName: Name of the missing propertymessage: Error messagename: "ConfigPropertyDoesNotExistError"
Example:
try {
config.read({ REQUIRED: undefined });
} catch (error) {
if (error instanceof ConfigPropertyDoesNotExistError) {
console.log(error.propertyName); // "REQUIRED"
}
}Advanced Usage
Custom Config Provider
You can create custom configuration providers by implementing the ConfigProvider interface:
import { Config, ConfigProvider } from "@filipgorny/config";
class CustomConfigProvider implements ConfigProvider {
private data: Record<string, any> = {};
constructor() {
// Load config from your custom source
this.data = loadFromCustomSource();
}
get(key: string): string | number | boolean | undefined {
return this.data[key];
}
has(key: string): boolean {
return key in this.data;
}
}
const config = new Config(new CustomConfigProvider());Dependency Injection Integration
The Config class works seamlessly with @filipgorny/di:
import { createContainer } from "@filipgorny/di";
import { DotEnvConfigProvider, Config } from "@filipgorny/config";
const container = createContainer();
// Register the provider
container.registerInstance("configProvider", new DotEnvConfigProvider());
// Register the config
container.register("config", Config);
// Use it
const config = container.get<Config>("config");Type Definitions
For better type safety, define your config interface:
interface AppConfig {
PORT: number;
DATABASE_URL: string;
API_KEY: string;
NODE_ENV: "development" | "production" | "test";
ENABLE_DEBUG: boolean;
}
const config = createDotEnvConfig();
const appConfig: AppConfig = config.read({
PORT: 3000,
DATABASE_URL: undefined,
API_KEY: undefined,
NODE_ENV: "development",
ENABLE_DEBUG: false,
});Best Practices
1. Centralize Configuration
Create a single configuration file that exports your app config:
// config/index.ts
export const config = createDotEnvConfig().read({
// All your config here
});2. Use Undefined for Required Properties
Always use undefined for properties that MUST exist:
const config = configProvider.read({
CRITICAL_API_KEY: undefined, // ✅ Will throw if missing
OPTIONAL_FEATURE: "off", // ✅ Has default
});3. Validate Early
Load and validate configuration at application startup:
// index.ts
import { config } from "./config";
// Config is validated here, before starting the app
startApplication(config);4. Use TypeScript
Define types for your configuration:
const config = configProvider.read({
PORT: 3000,
DEBUG: false,
});
type Config = typeof config;
// Config is: { PORT: number, DEBUG: boolean }5. Environment-Specific Files
Use different .env files for different environments:
const envFile =
process.env.NODE_ENV === "production"
? ".env.production"
: ".env.development";
const config = createDotEnvConfig(envFile);Environment Variables
The DotEnv provider automatically parses common types:
- Strings: Any text value
- Numbers: Automatically converted (e.g.,
PORT=3000→3000) - Booleans:
"true"or"false"(case-insensitive) →true/false
Example .env file:
# Server
PORT=3000
HOST=localhost
NODE_ENV=development
# Database
DATABASE_URL=postgresql://localhost/mydb
DATABASE_POOL_SIZE=10
# API Keys
OPENAI_API_KEY=sk-xxxxxxxxxxxx
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxx
# Feature Flags
ENABLE_CACHE=true
ENABLE_RATE_LIMITING=false
# Logging
LOG_LEVEL=debugMigration Guide
From Manual Validation
Before:
const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;
const apiKey = process.env.API_KEY;
if (!apiKey) {
throw new Error("API_KEY is required");
}
const config = { port, apiKey };After:
const config = createDotEnvConfig().read({
PORT: 3000,
API_KEY: undefined,
});From dotenv Package
Before:
import dotenv from "dotenv";
dotenv.config();
const config = {
port: parseInt(process.env.PORT || "3000"),
apiKey: process.env.API_KEY!,
};After:
import { createDotEnvConfig } from "@filipgorny/config";
const config = createDotEnvConfig().read({
PORT: 3000,
API_KEY: undefined,
});Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT
Author
Filip Gorny
