We use cookies to enhance your experience on the site
CodeWorlds

Configuration Management - managing Empire resources

Ave, quaestor of the Empire! Consul Caesar.js has entrusted you with managing all system resources. Different legions have different needs, different environments require different settings. Time to learn Configuration Management - the art of managing all settings of our Roman Empire!

What is Configuration Management?

In the Roman Empire you have different types of configuration:

  • Basic legion settings - name, capacity, combat strength
  • Environment parameters - development, production, testing
  • Secrets and keys - archive passwords, API keys
  • External settings - province addresses, fort coordinates
  • Feature flags - which features are available to legionaries

Configuration Management in NestJS allows you to elegantly manage all these settings.

Installation and basic configuration

1npm install @nestjs/config
2npm install joi # for schema validation

Basic configuration

1// app.module.ts
2import { Module } from '@nestjs/common';
3import { ConfigModule } from '@nestjs/config';
4
5@Module({
6 imports: [
7 ConfigModule.forRoot({
8 isGlobal: true,
9 envFilePath: ['.env.local', '.env'],
10 ignoreEnvFile: process.env.NODE_ENV === 'production',
11 }),
12 ],
13})
14export class AppModule {}

Environment files

1# .env
2# Basic configuration for the entire system
3NODE_ENV=development
4PORT=3000
5
6# Legion information
7LEGION_NAME=Legio X Equestris
8LEGION_COMMANDER=Caesar.js
9LEGION_CAPACITY=50
10
11# Database
12DATABASE_HOST=localhost
13DATABASE_PORT=5432
14DATABASE_NAME=imperium_tributes
15DATABASE_USERNAME=imperium_admin
16DATABASE_PASSWORD=roma_invicta_secret
17
18# API keys
19WEATHER_API_KEY=your_weather_api_key
20MAP_API_KEY=your_map_api_key
21TRIBUTE_TRACKER_SECRET=super_secret_key
22
23# Redis
24REDIS_HOST=localhost
25REDIS_PORT=6379
26
27# JWT
28JWT_SECRET=imperium_jwt_secret_key
29JWT_EXPIRES_IN=24h
30
31# External services
32TRIBUTE_EXCHANGE_URL=https://api.tribute-exchange.com
33LEGION_COMMUNICATION_URL=https://legion-comm.imperium-romanum.io
34
35# Feature flags
36ENABLE_ORACLE_PREDICTIONS=true
37ENABLE_WEATHER_PREDICTION=true
38ENABLE_ADVANCED_ENGINEERING=false
1# .env.production
2NODE_ENV=production
3PORT=80
4
5LEGION_NAME=Production Legion Prima
6DATABASE_HOST=prod-db.imperium-romanum.io
7DATABASE_PASSWORD=\${PROD_DB_PASSWORD}
8
9# In production we use external services
10REDIS_HOST=prod-redis.imperium-romanum.io
11TRIBUTE_EXCHANGE_URL=https://api.tribute-exchange.com/v2
12
13ENABLE_ADVANCED_ENGINEERING=true

Structured Configuration

Legion Configuration

1// config/legion.config.ts
2import { registerAs } from '@nestjs/config';
3
4export default registerAs('legion', () => ({
5 name: process.env.LEGION_NAME || 'Unknown Legion',
6 commander: process.env.LEGION_COMMANDER || 'Anonymous Commander',
7 capacity: parseInt(process.env.LEGION_CAPACITY, 10) || 30,
8 type: process.env.LEGION_TYPE || 'COHORT',
9 homeBase: process.env.HOME_BASE || 'Roma',
10 coordinates: {
11 latitude: parseFloat(process.env.LEGION_LATITUDE) || 0,
12 longitude: parseFloat(process.env.LEGION_LONGITUDE) || 0,
13 },
14 equipment: {
15 ballistae: parseInt(process.env.LEGION_BALLISTAE, 10) || 20,
16 standards: parseInt(process.env.LEGION_STANDARDS, 10) || 3,
17 storage: parseInt(process.env.LEGION_STORAGE, 10) || 1000,
18 },
19 features: {
20 oraclePredictions: process.env.ENABLE_ORACLE_PREDICTIONS === 'true',
21 weatherPrediction: process.env.ENABLE_WEATHER_PREDICTION === 'true',
22 advancedEngineering: process.env.ENABLE_ADVANCED_ENGINEERING === 'true',
23 }
24}));

Database Configuration

1// config/database.config.ts
2import { registerAs } from '@nestjs/config';
3
4export default registerAs('database', () => ({
5 type: 'postgres',
6 host: process.env.DATABASE_HOST || 'localhost',
7 port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
8 username: process.env.DATABASE_USERNAME || 'imperium',
9 password: process.env.DATABASE_PASSWORD || 'password',
10 database: process.env.DATABASE_NAME || 'imperium_tributes',
11 entities: ['dist/**/*.entity{.ts,.js}'],
12 synchronize: process.env.NODE_ENV !== 'production',
13 logging: process.env.NODE_ENV === 'development',
14 ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
15 migrations: ['dist/migrations/*{.ts,.js}'],
16 migrationsRun: process.env.NODE_ENV === 'production',
17 retryAttempts: 3,
18 retryDelay: 3000,
19}));

External Services Configuration

1// config/external-services.config.ts
2import { registerAs } from '@nestjs/config';
3
4export default registerAs('externalServices', () => ({
5 weatherApi: {
6 baseUrl: process.env.WEATHER_API_URL || 'https://api.openweathermap.org/data/2.5',
7 apiKey: process.env.WEATHER_API_KEY,
8 timeout: parseInt(process.env.WEATHER_API_TIMEOUT, 10) || 5000,
9 },
10 tributeExchange: {
11 baseUrl: process.env.TRIBUTE_EXCHANGE_URL || 'https://api.tribute-exchange.com',
12 secret: process.env.TRIBUTE_TRACKER_SECRET,
13 timeout: parseInt(process.env.TRIBUTE_EXCHANGE_TIMEOUT, 10) || 10000,
14 },
15 legionCommunication: {
16 baseUrl: process.env.LEGION_COMMUNICATION_URL,
17 channel: process.env.LEGION_CHANNEL || 'main',
18 encryptionKey: process.env.LEGION_ENCRYPTION_KEY,
19 },
20 maps: {
21 provider: process.env.MAP_PROVIDER || 'google',
22 apiKey: process.env.MAP_API_KEY,
23 cacheTime: parseInt(process.env.MAP_CACHE_TIME, 10) || 3600,
24 }
25}));

Schema Validation

1// config/validation.schema.ts
2import * as Joi from 'joi';
3
4export const validationSchema = Joi.object({
5 NODE_ENV: Joi.string()
6 .valid('development', 'production', 'test')
7 .default('development'),
8
9 PORT: Joi.number().default(3000),
10
11 // Legion configuration
12 LEGION_NAME: Joi.string().required(),
13 LEGION_COMMANDER: Joi.string().required(),
14 LEGION_CAPACITY: Joi.number().min(10).max(200).default(50),
15 LEGION_TYPE: Joi.string()
16 .valid('COHORT', 'LEGION', 'CENTURY', 'MANIPLE')
17 .default('COHORT'),
18
19 // Database
20 DATABASE_HOST: Joi.string().required(),
21 DATABASE_PORT: Joi.number().default(5432),
22 DATABASE_USERNAME: Joi.string().required(),
23 DATABASE_PASSWORD: Joi.string().required(),
24 DATABASE_NAME: Joi.string().required(),
25
26 // JWT
27 JWT_SECRET: Joi.string().min(32).required(),
28 JWT_EXPIRES_IN: Joi.string().default('24h'),
29
30 // Redis
31 REDIS_HOST: Joi.string().default('localhost'),
32 REDIS_PORT: Joi.number().default(6379),
33
34 // API Keys
35 WEATHER_API_KEY: Joi.string().when('NODE_ENV', {
36 is: 'production',
37 then: Joi.required(),
38 otherwise: Joi.optional()
39 }),
40
41 MAP_API_KEY: Joi.string().when('NODE_ENV', {
42 is: 'production',
43 then: Joi.required(),
44 otherwise: Joi.optional()
45 }),
46});
47
48// In app.module.ts
49@Module({
50 imports: [
51 ConfigModule.forRoot({
52 isGlobal: true,
53 validationSchema,
54 validationOptions: {
55 allowUnknown: true,
56 abortEarly: false,
57 },
58 load: [
59 legionConfig,
60 databaseConfig,
61 externalServicesConfig,
62 ],
63 }),
64 ],
65})
66export class AppModule {}

Using configuration in services

1// services/legion.service.ts
2import { Injectable } from '@nestjs/common';
3import { ConfigService } from '@nestjs/config';
4
5@Injectable()
6export class LegionService {
7 constructor(private configService: ConfigService) {}
8
9 getLegionInfo() {
10 return {
11 name: this.configService.get<string>('legion.name'),
12 commander: this.configService.get<string>('legion.commander'),
13 capacity: this.configService.get<number>('legion.capacity'),
14 type: this.configService.get<string>('legion.type'),
15 coordinates: this.configService.get('legion.coordinates'),
16 equipment: this.configService.get('legion.equipment'),
17 };
18 }
19
20 getFeatures() {
21 return this.configService.get('legion.features');
22 }
23
24 canUseOraclePredictions(): boolean {
25 return this.configService.get<boolean>('legion.features.oraclePredictions', false);
26 }
27
28 getStorageCapacity(): number {
29 return this.configService.get<number>('legion.equipment.storage', 1000);
30 }
31}

Typed Configuration Service

1// services/typed-config.service.ts
2import { Injectable } from '@nestjs/common';
3import { ConfigService } from '@nestjs/config';
4
5interface LegionConfig {
6 name: string;
7 commander: string;
8 capacity: number;
9 type: 'COHORT' | 'LEGION' | 'CENTURY' | 'MANIPLE';
10 homeBase: string;
11 coordinates: {
12 latitude: number;
13 longitude: number;
14 };
15 equipment: {
16 ballistae: number;
17 standards: number;
18 storage: number;
19 };
20 features: {
21 oraclePredictions: boolean;
22 weatherPrediction: boolean;
23 advancedEngineering: boolean;
24 };
25}
26
27@Injectable()
28export class TypedConfigService {
29 constructor(private configService: ConfigService) {}
30
31 get legion(): LegionConfig {
32 return this.configService.get<LegionConfig>('legion')!;
33 }
34
35 get database() {
36 return {
37 host: this.configService.get<string>('database.host')!,
38 port: this.configService.get<number>('database.port')!,
39 username: this.configService.get<string>('database.username')!,
40 password: this.configService.get<string>('database.password')!,
41 database: this.configService.get<string>('database.database')!,
42 };
43 }
44
45 get isDevelopment(): boolean {
46 return this.configService.get<string>('NODE_ENV') === 'development';
47 }
48
49 get isProduction(): boolean {
50 return this.configService.get<string>('NODE_ENV') === 'production';
51 }
52}

Dynamic Configuration

1// services/feature-flag.service.ts
2@Injectable()
3export class FeatureFlagService {
4 private features = new Map<string, boolean>();
5
6 constructor(private configService: ConfigService) {
7 this.loadFeatures();
8 }
9
10 private loadFeatures() {
11 // Load from configuration
12 this.features.set('oraclePredictions',
13 this.configService.get<boolean>('legion.features.oraclePredictions', false));
14 this.features.set('weatherPrediction',
15 this.configService.get<boolean>('legion.features.weatherPrediction', false));
16 this.features.set('advancedEngineering',
17 this.configService.get<boolean>('legion.features.advancedEngineering', false));
18 }
19
20 isEnabled(feature: string): boolean {
21 return this.features.get(feature) || false;
22 }
23
24 enable(feature: string) {
25 this.features.set(feature, true);
26 console.log(\`Feature enabled: \${feature}\`);
27 }
28
29 disable(feature: string) {
30 this.features.set(feature, false);
31 console.log(\`Feature disabled: \${feature}\`);
32 }
33
34 getAllFeatures() {
35 return Object.fromEntries(this.features);
36 }
37}
38
39// Guard for feature flags
40@Injectable()
41export class FeatureGuard implements CanActivate {
42 constructor(
43 private featureFlagService: FeatureFlagService,
44 private reflector: Reflector,
45 ) {}
46
47 canActivate(context: ExecutionContext): boolean {
48 const requiredFeature = this.reflector.get<string>('feature', context.getHandler());
49
50 if (!requiredFeature) {
51 return true;
52 }
53
54 return this.featureFlagService.isEnabled(requiredFeature);
55 }
56}
57
58// Decorator for feature flags
59export const RequireFeature = (feature: string) => SetMetadata('feature', feature);
60
61// Usage
62@Controller('advanced')
63export class AdvancedController {
64 @Get('engineering')
65 @RequireFeature('advancedEngineering')
66 @UseGuards(FeatureGuard)
67 getAdvancedEngineering() {
68 return { message: 'Advanced engineering available!' };
69 }
70}

Environment-specific Services

1// services/external-api.service.ts
2@Injectable()
3export class ExternalApiService {
4 private readonly weatherApiConfig;
5 private readonly tributeExchangeConfig;
6
7 constructor(private configService: ConfigService) {
8 this.weatherApiConfig = this.configService.get('externalServices.weatherApi');
9 this.tributeExchangeConfig = this.configService.get('externalServices.tributeExchange');
10 }
11
12 async getWeatherForecast(coordinates: { lat: number; lng: number }) {
13 if (!this.weatherApiConfig.apiKey) {
14 console.warn('Weather API key not configured, using mock data');
15 return this.getMockWeather(coordinates);
16 }
17
18 const url = \`\${this.weatherApiConfig.baseUrl}/weather?lat=\${coordinates.lat}&lon=\${coordinates.lng}&appid=\${this.weatherApiConfig.apiKey}\`
1
2 try {
3 const response = await fetch(url, {
4 timeout: this.weatherApiConfig.timeout
5 });
6 return await response.json();
7 } catch (error) {
8 console.error('Weather API error:', error);
9 return this.getMockWeather(coordinates);
10 }
11 }
12
13 private getMockWeather(coordinates: any) {
14 return {
15 weather: 'sunny',
16 temperature: 25,
17 windSpeed: 10,
18 coordinates,
19 source: 'mock'
20 };
21 }
22}

Configuration Management is the foundation of a well-functioning application - it enables flexibility, security, and ease of managing different environments. Like a good Empire administrator who knows where every resource is located!

Go to CodeWorlds