CtrlK
BlogDocsLog inGet started
Tessl Logo

jbvc/backend-dev-guidelines

Opinionated backend development standards for Node.js + Express + TypeScript microservices. Covers layered architecture, BaseController pattern, dependency injection, Prisma repositories, Zod validation, unifiedConfig, Sentry error tracking, async safety, and testing discipline.

71

Quality

71%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

configuration.mdresources/

Configuration Management - UnifiedConfig Pattern

Complete guide to managing configuration in backend microservices.

Table of Contents

  • UnifiedConfig Overview
  • NEVER Use process.env Directly
  • Configuration Structure
  • Environment-Specific Configs
  • Secrets Management
  • 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

// ❌ 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:

// 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:

const port = config.server.port; // number, guaranteed
const timeout = config.timeouts.default; // number, with fallback

Configuration Structure

UnifiedConfig Interface

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

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

[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

# .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
config.ini
.env
sentry.ini
*.pem
*.key

Use Environment Variables in Production

// 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

grep -r "process.env" blog-api/src/ --include="*.ts" | wc -l

Migration Example

Before:

// 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:

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
  • testing-guide.md

resources

architecture-overview.md

async-and-errors.md

complete-examples.md

configuration.md

database-patterns.md

middleware-guide.md

routing-and-controllers.md

sentry-and-monitoring.md

services-and-repositories.md

testing-guide.md

validation-patterns.md

SKILL.md

tile.json