feat: validate backend runtime configuration

This commit is contained in:
Bastian Wagner
2026-08-17 13:26:38 +02:00
parent a08eecd4e3
commit ceaef3028f
13 changed files with 83 additions and 18 deletions

View File

@@ -1,9 +1,10 @@
import { Module } from '@nestjs/common';
import { ConfigurationModule } from '../../../libs/configuration/src';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
imports: [ConfigurationModule],
controllers: [AppController],
providers: [AppService],
})

View File

@@ -3,6 +3,7 @@
"rootDir": "../../..",
"testEnvironment": "node",
"testRegex": "apps/api/test/.*\\.e2e-spec\\.ts$",
"setupFiles": ["<rootDir>/test/setup-env.ts"],
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}

View File

@@ -1,8 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": false,
"outDir": "../../dist/apps/api"
"declaration": false
},
"exclude": ["node_modules", "dist", "test", "**/*.spec.ts"]
}

View File

@@ -1,4 +1,7 @@
import { Module } from '@nestjs/common';
import { ConfigurationModule } from '../../../libs/configuration/src';
@Module({})
@Module({
imports: [ConfigurationModule],
})
export class WorkerModule {}

View File

@@ -1,8 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": false,
"outDir": "../../dist/apps/worker"
"declaration": false
},
"exclude": ["node_modules", "dist", "test", "**/*.spec.ts"]
}

View File

@@ -0,0 +1,9 @@
import { Global, Module } from '@nestjs/common';
import { appEnvironmentProvider } from './environment';
@Global()
@Module({
providers: [appEnvironmentProvider],
exports: [appEnvironmentProvider],
})
export class ConfigurationModule {}

View File

@@ -0,0 +1,25 @@
import { loadEnvironment } from './environment';
describe('loadEnvironment', () => {
it('requires database and redis URLs', () => {
expect(() => loadEnvironment({})).toThrow('DATABASE_URL');
});
it('returns build metadata without secrets', () => {
expect(
loadEnvironment({
DATABASE_URL: 'postgresql://u:p@postgres:5432/db',
REDIS_URL: 'redis://redis:6379',
APP_VERSION: '1.2.3',
TEAMCITY_BUILD_NUMBER: '42',
SOURCE_REVISION: 'abc123',
}),
).toEqual({
databaseUrl: 'postgresql://u:p@postgres:5432/db',
redisUrl: 'redis://redis:6379',
appVersion: '1.2.3',
teamCityBuildNumber: '42',
sourceRevision: 'abc123',
});
});
});

View File

@@ -0,0 +1,30 @@
export interface AppEnvironment {
databaseUrl: string;
redisUrl: string;
appVersion: string;
teamCityBuildNumber: string;
sourceRevision: string;
}
function required(env: NodeJS.ProcessEnv, name: string): string {
const value = env[name]?.trim();
if (!value) throw new Error(`Missing required environment variable: ${name}`);
return value;
}
export function loadEnvironment(env: NodeJS.ProcessEnv): AppEnvironment {
return {
databaseUrl: required(env, 'DATABASE_URL'),
redisUrl: required(env, 'REDIS_URL'),
appVersion: env.APP_VERSION?.trim() || 'dev',
teamCityBuildNumber: env.TEAMCITY_BUILD_NUMBER?.trim() || 'local',
sourceRevision: env.SOURCE_REVISION?.trim() || 'local',
};
}
export const APP_ENVIRONMENT = Symbol('APP_ENVIRONMENT');
export const appEnvironmentProvider = {
provide: APP_ENVIRONMENT,
useFactory: () => loadEnvironment(process.env),
};

View File

@@ -0,0 +1,2 @@
export * from './environment';
export * from './configuration.module';

View File

@@ -5,7 +5,7 @@
"root": "apps/api",
"sourceRoot": "apps/api/src",
"compilerOptions": {
"deleteOutDir": true
"deleteOutDir": false
},
"projects": {
"api": {

View File

@@ -13,8 +13,8 @@
"start": "nest start api",
"start:dev": "nest start api --watch",
"start:debug": "nest start api --debug --watch",
"start:api": "node dist/apps/api/main.js",
"start:worker": "node dist/apps/worker/main.js",
"start:api": "node dist/apps/api/src/main.js",
"start:worker": "node dist/apps/worker/src/main.js",
"lint": "eslint \"{apps,libs}/**/*.ts\" --fix",
"test": "jest --runInBand",
"test:watch": "jest --watch",
@@ -63,13 +63,10 @@
"rootDir": ".",
"testRegex": ".*\\.spec\\.ts$",
"testPathIgnorePatterns": ["<rootDir>/node_modules/", "<rootDir>/dist/"],
"setupFiles": ["<rootDir>/test/setup-env.ts"],
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"moduleNameMapper": {
"^@travel/configuration$": "<rootDir>/libs/configuration/src",
"^@travel/infrastructure$": "<rootDir>/libs/infrastructure/src"
},
"collectCoverageFrom": [
"apps/**/*.(t|j)s",
"libs/**/*.(t|j)s"

View File

@@ -0,0 +1,2 @@
process.env.DATABASE_URL ??= 'postgresql://test:test@localhost:5432/test';
process.env.REDIS_URL ??= 'redis://localhost:6379';

View File

@@ -13,6 +13,7 @@
"target": "ES2023",
"sourceMap": true,
"outDir": "./dist",
"rootDir": ".",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
@@ -20,10 +21,6 @@
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false,
"paths": {
"@travel/configuration": ["libs/configuration/src"],
"@travel/infrastructure": ["libs/infrastructure/src"]
}
"noFallthroughCasesInSwitch": false
}
}