@unidev-hub/config
v1.0.67
Published
Configuration management for microservices
Readme
@unidev-hub/logger
A standardized logging package for TypeScript microservices, built on top of Pino.
Features
- 🚀 High-performance logging using Pino
- 🔍 Request ID tracking for service correlation
- 🌐 Express middleware for request/response logging
- 🛡️ Consistent error logging format
- 🧩 Contextual child loggers
Installation
npm install @unidev-hub/loggerUsage# @unidev-hub/config
A comprehensive configuration management package for microservices.
Features
- 🔀 Multiple configuration sources (environment, files, memory)
- 🔄 Dynamic configuration reloading
- 🔒 Secrets management and masking
- 🚩 Feature flags with percentage rollouts
- ✅ Configuration validation using JSON Schema
- 🔄 Environment-specific configurations
- 🎯 Change listeners for configuration updates
Installation
npm install @unidev-hub/configBasic Usage
import { createDefaultConfigManager } from '@unidev-hub/config';
import { createLogger } from '@unidev-hub/logger';
const logger = createLogger({ serviceName: 'user-service' });
// Create a config manager with default providers
const config = createDefaultConfigManager({
environment: process.env.NODE_ENV || 'development',
configFile: './config/config.yaml',
envPrefix: 'APP_',
secretKeys: ['database.password', 'apiKeys.stripe'],
logger,
});
// Access configuration values
const port = config.get<number>('server.port', 3000);
const dbUrl = config.get<string>('database.url');
// Listen for configuration changes
config.onChange((changedKeys) => {
if (changedKeys.includes('server.port')) {
const newPort = config.get<number>('server.port');
logger.info('Server port changed', { newPort });
// Restart server with new port
}
});
// Feature flags
config.registerFeatureFlag({
key: 'newCheckout',
enabled: true,
description: 'New checkout flow',
percentage: 50, // Enable for 50% of users
});
// Check if feature is enabled
if (config.isFeatureEnabled('newCheckout', { userId: '123' })) {
// Use new checkout flow
} else {
// Use old checkout flow
}Configuration Sources
Environment Variables
import { EnvProvider, createConfigManager } from '@unidev-hub/config';
const envProvider = new EnvProvider({
prefix: 'APP_', // Only include vars with this prefix
path: '.env', // Load from .env file
logger,
});
const config = createConfigManager({
providers: [envProvider],
logger,
});
// Usage (with APP_SERVER_PORT=3000 in environment)
const port = config.get('server.port'); // 3000YAML/JSON Files
import { FileProvider, createConfigManager } from '@unidev-hub/config';
const fileProvider = new FileProvider({
filePath: './config/config.yaml',
watch: true, // Auto-reload on file changes
logger,
});
const config = createConfigManager({
providers: [fileProvider],
logger,
});
// With config.yaml containing:
// server:
// port: 3000
const port = config.get('server.port'); // 3000In-Memory Configuration
import { MemoryProvider, createConfigManager } from '@unidev-hub/config';
const memoryProvider = new MemoryProvider({
initialConfig: {
server: {
port: 3000,
},
},
logger,
});
const config = createConfigManager({
providers: [memoryProvider],
logger,
});
// Update configuration at runtime
memoryProvider.set('server.port', 4000);Priority Order
When using multiple providers, later providers override earlier ones:
import {
EnvProvider,
FileProvider,
MemoryProvider,
createConfigManager
} from '@unidev-hub/config';
const config = createConfigManager({
providers: [
// Priority order (lowest to highest):
new FileProvider({ filePath: './config/default.yaml' }), // Base config
new FileProvider({ filePath: './config/local.yaml' }), // Local overrides
new EnvProvider({ prefix: 'APP_' }), // Environment vars
new MemoryProvider(), // Runtime changes
],
logger,
});Configuration Validation
import { createConfigManager, createDefaultConfigManager } from '@unidev-hub/config';
import { JSONSchemaType } from 'ajv';
// Define configuration schema
interface AppConfig {
server: {
port: number;
host: string;
};
database: {
url: string;
maxConnections: number;
};
}
const schema: JSONSchemaType<AppConfig> = {
type: 'object',
required: ['server', 'database'],
properties: {
server: {
type: 'object',
required: ['port', 'host'],
properties: {
port: { type: 'number', minimum: 1024, maximum: 65535 },
host: { type: 'string' },
},
},
database: {
type: 'object',
required: ['url'],
properties: {
url: { type: 'string' },
maxConnections: { type: 'number', default: 10 },
},
},
},
};
// Create config manager with validation
const config = createDefaultConfigManager({
schema, // Will validate against this schema
logger,
});
// Config will be validated on load and defaults will be appliedSecret Management
import { createDefaultConfigManager } from '@unidev-hub/config';
const config = createDefaultConfigManager({
secretKeys: ['database.password', 'apiKeys.stripe'],
logger,
});
// Add more secret keys
config.registerSecretKeys(['auth.jwt.secret', 'sms.apiKey']);
// Get masked configuration (for logging)
const maskedConfig = config.getMaskedConfig();
console.log(maskedConfig);
// Output: { database: { password: 'p****d' }, apiKeys: { stripe: '3****e' } }Feature Flags
import { createDefaultConfigManager } from '@unidev-hub/config';
const config = createDefaultConfigManager({ logger });
// Register feature flags
config.registerFeatureFlags([
{
key: 'newUI',
enabled: true,
description: 'New user interface',
},
{
key: 'betaFeature',
enabled: true,
percentage: 20, // Only enable for 20% of users
description: 'Beta feature',
environments: {
production: false, // Disabled in production
},
},
{
key: 'adminDashboard',
enabled: false,
description: 'Admin dashboard',
group: 'admin',
},
]);
// Check if features are enabled
if (config.isFeatureEnabled('newUI')) {
// Use new UI
}
// User-specific features (deterministic based on user ID)
if (config.isFeatureEnabled('betaFeature', { userId: '123' })) {
// Show beta feature
}
// List all feature flags
const featureFlags = config.getFeatureFlags().getAllFlags();
// Update a feature flag
config.getFeatureFlags().updateFlag('adminDashboard', { enabled: true });Variable Interpolation
import { createDefaultConfigManager } from '@unidev-hub/config';
// Environment: NODE_ENV=development, DB_HOST=localhost
const config = createDefaultConfigManager({ logger });
// Set up config with references to environment variables
config.set('database', {
host: '${DB_HOST:localhost}', // Use DB_HOST or default to localhost
port: 5432,
name: 'myapp_${env}', // Will be replaced with current environment
});
// Get interpolated values
const dbHost = config.get('database.host'); // 'localhost'
const dbName = config.get('database.name'); // 'myapp_development'Advanced Usage
Custom Configuration Provider
import { ConfigProvider, createConfigManager } from '@unidev-hub/config';
// Implement a custom provider (e.g., from a remote service)
class RemoteConfigProvider implements ConfigProvider {
private apiClient: any;
private changeListeners: Array<() => void> = [];
constructor(apiUrl: string) {
this.apiClient = { /* API client implementation */ };
// Poll for changes
setInterval(() => this.checkForChanges(), 60000);
}
async load(): Promise<Record<string, any>> {
// Fetch configuration from remote API
const config = await this.apiClient.getConfig();
return config;
}
onChange(listener: () => void): void {
this.changeListeners.push(listener);
}
private async checkForChanges(): Promise<void> {
// Check if configuration has changed
const hasChanged = await this.apiClient.hasConfigChanged();
if (hasChanged) {
this.changeListeners.forEach(listener => listener());
}
}
}
// Use the custom provider
const configManager = createConfigManager({
providers: [
new RemoteConfigProvider('https://config-api.example.com'),
],
logger,
});Configuration Layering
import {
createConfigManager,
FileProvider,
EnvProvider,
MemoryProvider
} from '@unidev-hub/config';
// Environment-specific configuration
const environment = process.env.NODE_ENV || 'development';
const configManager = createConfigManager({
environment,
providers: [
// Base configuration
new FileProvider({
filePath: './config/default.yaml',
watch: true,
logger
}),
// Environment-specific configuration
new FileProvider({
filePath: `./config/${environment}.yaml`,
watch: true,
throwOnMissing: false, // Don't fail if file doesn't exist
logger
}),
// Local overrides (not in version control)
new FileProvider({
filePath: './config/local.yaml',
watch: true,
throwOnMissing: false,
logger
}),
// Environment variables
new EnvProvider({ prefix: 'APP_', logger }),
// Runtime configuration
new MemoryProvider({ logger }),
],
logger,
});License
MIT
Basic Usage
import { createLogger } from '@unidev-hub/logger';
const logger = createLogger({
serviceName: 'user-service',
level: 'info',
prettyPrint: process.env.NODE_ENV === 'development'
});
logger.info('Server started', { port: 3000 });
logger.error('Failed to connect to database', new Error('Connection timeout'));
// Or with additional context
logger.error('Failed to connect to database', new Error('Connection timeout'), { host: 'db-server-1' });With Express
import express from 'express';
import { createLogger, createRequestLogger, getRequestLogger } from '@unidev-hub/logger';
const app = express();
const logger = createLogger({ serviceName: 'api-gateway' });
// Apply middleware
app.use(createRequestLogger(logger, {
getUserId: (req) => req.user?.id,
skipPaths: ['/health', '/metrics']
}));
// Use in route handlers
app.get('/users', (req, res) => {
const requestLogger = getRequestLogger(req);
requestLogger.info('Fetching users');
// Your logic here
res.json({ users: [] });
});
app.listen(3000, () => {
logger.info('Server started', { port: 3000 });
});Error Logging
// With Error object
logger.error('Failed to connect to database', new Error('Connection timeout'));
// With context object
logger.error('User validation failed', { userId: '123', reason: 'Invalid email' });
// With both Error and additional context
logger.error('Payment processing failed', new Error('Gateway timeout'), { orderId: 'ORD-123' });Child Loggers
function processOrder(orderId: string, userId: string) {
const orderLogger = logger.child({ orderId, userId });
orderLogger.info('Processing order');
// All logs from this logger will include orderId and userId
orderLogger.debug('Validating payment');
orderLogger.info('Order processed successfully');
}Configuration
The logger accepts the following configuration options:
| Option | Type | Description | Default | |--------|------|-------------|---------| | serviceName | string | Name of the service | Required | | level | string | Minimum log level | 'info' or LOG_LEVEL env var | | prettyPrint | boolean | Format logs for human readability | true in development | | environment | string | Environment name | NODE_ENV or 'development' | | additionalFields | object | Extra fields to add to all logs | {} |
Request Logger Middleware Options
| Option | Type | Description | Default | |--------|------|-------------|---------| | getUserId | Function | Extract user ID from request | undefined | | logRequestBody | boolean | Include request body in logs | false | | logResponseBody | boolean | Include response body in logs | false | | skipPaths | string[] | Paths to exclude from logging | [] |
Best Practices
- Use child loggers to add context to related log entries
- Include error objects in error logs for stack traces
- Use appropriate log levels:
trace: Very detailed debuggingdebug: Debugging informationinfo: Normal operationswarn: Warning conditionserror: Error conditionsfatal: Critical failures
License
MIT
