Environment Configuration Management with Typescript
Managing environment-specific configurations is a common challenge in software development. This challenge asks you to create a robust and type-safe system for defining and accessing environment configurations in Typescript, ensuring that your application behaves correctly across different environments (e.g., development, staging, production). This will improve code maintainability and reduce errors caused by incorrect environment settings.
Problem Description
You need to design and implement a Typescript system that allows you to define environment configurations and access them safely through types. The system should:
- Define Environment Types: Create a type that represents different environments (e.g.,
Development,Staging,Production). - Define Configuration Types: Create a type that defines the structure of your environment configurations. This type should be generic, allowing you to specify the configuration properties for each environment.
- Create an Environment Configuration Object: Implement a function that takes an environment type and returns a configuration object of the defined type. This function should use a switch statement or similar logic to select the appropriate configuration based on the environment.
- Type Safety: Ensure that the system provides type safety when accessing configuration values. Attempting to access a configuration property that doesn't exist for a given environment should result in a Typescript error.
Expected Behavior:
The system should allow you to define configurations for different environments and access them safely, with Typescript providing compile-time checks to prevent errors.
Edge Cases to Consider:
- What happens if an environment is not defined in the configuration? (Should ideally result in a compile-time error or a default configuration).
- How can you handle optional configuration properties?
- How can you ensure that the configuration object is of the correct type?
Examples
Example 1:
// Configuration Types
type AppConfig = {
apiUrl: string;
port: number;
debugMode: boolean;
apiKey?: string; // Optional property
};
type DevelopmentConfig = AppConfig & {
databaseUrl: string;
};
type StagingConfig = AppConfig & {
featureFlags: string[];
};
type ProductionConfig = AppConfig;
// Environment Types
type Environment = 'Development' | 'Staging' | 'Production';
// Function to get configuration
function getEnvironmentConfig(environment: Environment): AppConfig {
switch (environment) {
case 'Development':
return {
apiUrl: 'http://localhost:3000/api',
port: 3000,
debugMode: true,
databaseUrl: 'localhost:5432',
} as DevelopmentConfig;
case 'Staging':
return {
apiUrl: 'https://staging.example.com/api',
port: 8080,
debugMode: false,
featureFlags: ['new-feature-1', 'new-feature-2'],
} as StagingConfig;
case 'Production':
return {
apiUrl: 'https://example.com/api',
port: 443,
debugMode: false,
} as ProductionConfig;
default:
throw new Error(`Unsupported environment: ${environment}`);
}
}
// Usage
const developmentConfig = getEnvironmentConfig('Development');
console.log(developmentConfig.apiUrl); // Output: http://localhost:3000/api
console.log(developmentConfig.databaseUrl); // Output: localhost:5432
// The following would cause a Typescript error:
// console.log(developmentConfig.apiKey); // Error: Property 'apiKey' does not exist on type 'DevelopmentConfig'
Example 2:
// Simplified Example
type Config = {
message: string;
};
type DevConfig = Config & {
logLevel: 'debug';
};
type ProdConfig = Config & {
logLevel: 'error';
};
type EnvironmentType = 'dev' | 'prod';
function getConfig(env: EnvironmentType): Config {
switch (env) {
case 'dev':
return { message: 'Dev Message', logLevel: 'debug' } as DevConfig;
case 'prod':
return { message: 'Prod Message', logLevel: 'error' } as ProdConfig;
default:
throw new Error('Invalid environment');
}
}
const devConfig = getConfig('dev');
console.log(devConfig.message); // Output: Dev Message
console.log(devConfig.logLevel); // Output: debug
Constraints
- The solution must be written in Typescript.
- The configuration system must be type-safe, preventing access to non-existent configuration properties at compile time.
- The solution should handle at least three different environment types (Development, Staging, Production).
- The configuration object should be generic, allowing for different configuration properties for each environment.
- The code should be well-structured and easy to understand.
Notes
- Consider using intersection types (
&) to combine base configuration types with environment-specific properties. - Think about how to handle default configurations if an environment is not explicitly defined.
- The
getEnvironmentConfigfunction should throw an error if an unsupported environment is provided. - Focus on creating a flexible and maintainable system that can be easily extended to support additional environments and configuration properties. Avoid hardcoding environment-specific values directly in the application logic; instead, retrieve them from the configuration object.