Initial commit: The Ultimate Antigravity Skills Collection (58 Skills)
This commit is contained in:
275
skills/backend-dev-guidelines/resources/configuration.md
Normal file
275
skills/backend-dev-guidelines/resources/configuration.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# Configuration Management - UnifiedConfig Pattern
|
||||
|
||||
Complete guide to managing configuration in backend microservices.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [UnifiedConfig Overview](#unifiedconfig-overview)
|
||||
- [NEVER Use process.env Directly](#never-use-processenv-directly)
|
||||
- [Configuration Structure](#configuration-structure)
|
||||
- [Environment-Specific Configs](#environment-specific-configs)
|
||||
- [Secrets Management](#secrets-management)
|
||||
- [Migration Guide](#migration-guide)
|
||||
|
||||
---
|
||||
|
||||
## UnifiedConfig Overview
|
||||
|
||||
### Why UnifiedConfig?
|
||||
|
||||
**Problems with process.env:**
|
||||
- ❌ No type safety
|
||||
- ❌ No validation
|
||||
- ❌ Hard to test
|
||||
- ❌ Scattered throughout code
|
||||
- ❌ No default values
|
||||
- ❌ Runtime errors for typos
|
||||
|
||||
**Benefits of unifiedConfig:**
|
||||
- ✅ Type-safe configuration
|
||||
- ✅ Single source of truth
|
||||
- ✅ Validated at startup
|
||||
- ✅ Easy to test with mocks
|
||||
- ✅ Clear structure
|
||||
- ✅ Fallback to environment variables
|
||||
|
||||
---
|
||||
|
||||
## NEVER Use process.env Directly
|
||||
|
||||
### The Rule
|
||||
|
||||
```typescript
|
||||
// ❌ NEVER DO THIS
|
||||
const timeout = parseInt(process.env.TIMEOUT_MS || '5000');
|
||||
const dbHost = process.env.DB_HOST || 'localhost';
|
||||
|
||||
// ✅ ALWAYS DO THIS
|
||||
import { config } from './config/unifiedConfig';
|
||||
const timeout = config.timeouts.default;
|
||||
const dbHost = config.database.host;
|
||||
```
|
||||
|
||||
### Why This Matters
|
||||
|
||||
**Example of problems:**
|
||||
```typescript
|
||||
// Typo in environment variable name
|
||||
const host = process.env.DB_HSOT; // undefined! No error!
|
||||
|
||||
// Type safety
|
||||
const port = process.env.PORT; // string! Need parseInt
|
||||
const timeout = parseInt(process.env.TIMEOUT); // NaN if not set!
|
||||
```
|
||||
|
||||
**With unifiedConfig:**
|
||||
```typescript
|
||||
const port = config.server.port; // number, guaranteed
|
||||
const timeout = config.timeouts.default; // number, with fallback
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Structure
|
||||
|
||||
### UnifiedConfig Interface
|
||||
|
||||
```typescript
|
||||
export interface UnifiedConfig {
|
||||
database: {
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
database: string;
|
||||
};
|
||||
server: {
|
||||
port: number;
|
||||
sessionSecret: string;
|
||||
};
|
||||
tokens: {
|
||||
jwt: string;
|
||||
inactivity: string;
|
||||
internal: string;
|
||||
};
|
||||
keycloak: {
|
||||
realm: string;
|
||||
client: string;
|
||||
baseUrl: string;
|
||||
secret: string;
|
||||
};
|
||||
aws: {
|
||||
region: string;
|
||||
emailQueueUrl: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
};
|
||||
sentry: {
|
||||
dsn: string;
|
||||
environment: string;
|
||||
tracesSampleRate: number;
|
||||
};
|
||||
// ... more sections
|
||||
}
|
||||
```
|
||||
|
||||
### Implementation Pattern
|
||||
|
||||
**File:** `/blog-api/src/config/unifiedConfig.ts`
|
||||
|
||||
```typescript
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ini from 'ini';
|
||||
|
||||
const configPath = path.join(__dirname, '../../config.ini');
|
||||
const iniConfig = ini.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||
|
||||
export const config: UnifiedConfig = {
|
||||
database: {
|
||||
host: iniConfig.database?.host || process.env.DB_HOST || 'localhost',
|
||||
port: parseInt(iniConfig.database?.port || process.env.DB_PORT || '3306'),
|
||||
username: iniConfig.database?.username || process.env.DB_USER || 'root',
|
||||
password: iniConfig.database?.password || process.env.DB_PASSWORD || '',
|
||||
database: iniConfig.database?.database || process.env.DB_NAME || 'blog_dev',
|
||||
},
|
||||
server: {
|
||||
port: parseInt(iniConfig.server?.port || process.env.PORT || '3002'),
|
||||
sessionSecret: iniConfig.server?.sessionSecret || process.env.SESSION_SECRET || 'dev-secret',
|
||||
},
|
||||
// ... more configuration
|
||||
};
|
||||
|
||||
// Validate critical config
|
||||
if (!config.tokens.jwt) {
|
||||
throw new Error('JWT secret not configured!');
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Read from config.ini first
|
||||
- Fallback to process.env
|
||||
- Default values for development
|
||||
- Validation at startup
|
||||
- Type-safe access
|
||||
|
||||
---
|
||||
|
||||
## Environment-Specific Configs
|
||||
|
||||
### config.ini Structure
|
||||
|
||||
```ini
|
||||
[database]
|
||||
host = localhost
|
||||
port = 3306
|
||||
username = root
|
||||
password = password1
|
||||
database = blog_dev
|
||||
|
||||
[server]
|
||||
port = 3002
|
||||
sessionSecret = your-secret-here
|
||||
|
||||
[tokens]
|
||||
jwt = your-jwt-secret
|
||||
inactivity = 30m
|
||||
internal = internal-api-token
|
||||
|
||||
[keycloak]
|
||||
realm = myapp
|
||||
client = myapp-client
|
||||
baseUrl = http://localhost:8080
|
||||
secret = keycloak-client-secret
|
||||
|
||||
[sentry]
|
||||
dsn = https://your-sentry-dsn
|
||||
environment = development
|
||||
tracesSampleRate = 0.1
|
||||
```
|
||||
|
||||
### Environment Overrides
|
||||
|
||||
```bash
|
||||
# .env file (optional overrides)
|
||||
DB_HOST=production-db.example.com
|
||||
DB_PASSWORD=secure-password
|
||||
PORT=80
|
||||
```
|
||||
|
||||
**Precedence:**
|
||||
1. config.ini (highest priority)
|
||||
2. process.env variables
|
||||
3. Hard-coded defaults (lowest priority)
|
||||
|
||||
---
|
||||
|
||||
## Secrets Management
|
||||
|
||||
### DO NOT Commit Secrets
|
||||
|
||||
```gitignore
|
||||
# .gitignore
|
||||
config.ini
|
||||
.env
|
||||
sentry.ini
|
||||
*.pem
|
||||
*.key
|
||||
```
|
||||
|
||||
### Use Environment Variables in Production
|
||||
|
||||
```typescript
|
||||
// Development: config.ini
|
||||
// Production: Environment variables
|
||||
|
||||
export const config: UnifiedConfig = {
|
||||
database: {
|
||||
password: process.env.DB_PASSWORD || iniConfig.database?.password || '',
|
||||
},
|
||||
tokens: {
|
||||
jwt: process.env.JWT_SECRET || iniConfig.tokens?.jwt || '',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### Find All process.env Usage
|
||||
|
||||
```bash
|
||||
grep -r "process.env" blog-api/src/ --include="*.ts" | wc -l
|
||||
```
|
||||
|
||||
### Migration Example
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
// Scattered throughout code
|
||||
const timeout = parseInt(process.env.OPENID_HTTP_TIMEOUT_MS || '15000');
|
||||
const keycloakUrl = process.env.KEYCLOAK_BASE_URL;
|
||||
const jwtSecret = process.env.JWT_SECRET;
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
import { config } from './config/unifiedConfig';
|
||||
|
||||
const timeout = config.keycloak.timeout;
|
||||
const keycloakUrl = config.keycloak.baseUrl;
|
||||
const jwtSecret = config.tokens.jwt;
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Type-safe
|
||||
- Centralized
|
||||
- Easy to test
|
||||
- Validated at startup
|
||||
|
||||
---
|
||||
|
||||
**Related Files:**
|
||||
- [SKILL.md](SKILL.md)
|
||||
- [testing-guide.md](testing-guide.md)
|
||||
Reference in New Issue
Block a user