Development Standards
Development standards establish consistent conventions for code organisation, naming, error handling, and logging that apply across languages and frameworks. This reference provides lookup tables and specifications for day-to-day development decisions.
Naming Conventions
Naming conventions create predictable identifiers that communicate purpose without requiring developers to read implementation details. The conventions below apply regardless of programming language, with language-specific overrides where ecosystem norms differ substantially.
General Identifier Rules
All identifiers use ASCII characters exclusively. Unicode characters in identifiers create encoding issues across toolchains, complicate search operations, and cause problems in environments with limited font support.
| Identifier type | Convention | Length limit | Examples |
|---|---|---|---|
| Variable (local) | camelCase or snake_case per language | 30 characters | userCount, user_count |
| Constant | SCREAMING_SNAKE_CASE | 40 characters | MAX_RETRY_COUNT, DEFAULT_TIMEOUT_MS |
| Function/method | camelCase or snake_case per language | 40 characters | calculateTotal, calculate_total |
| Class/type | PascalCase | 40 characters | UserRepository, PaymentService |
| Interface | PascalCase, no prefix | 40 characters | Serializable, EventHandler |
| Enum | PascalCase for type, UPPER_CASE for values | 30 characters | Status.PENDING, Status.APPROVED |
| File | kebab-case or snake_case per ecosystem | 50 characters | user-service.js, user_service.py |
| Directory | kebab-case | 30 characters | data-access, api-handlers |
| Database table | snake_case, plural | 30 characters | users, payment_records |
| Database column | snake_case | 30 characters | created_at, user_id |
| API endpoint | kebab-case, plural nouns | 50 characters | /api/v1/user-accounts |
| Environment variable | SCREAMING_SNAKE_CASE | 50 characters | DATABASE_URL, API_SECRET_KEY |
Language-Specific Overrides
Each programming language has ecosystem conventions that override the general rules. Following ecosystem conventions ensures code integrates with libraries and frameworks without friction.
| Language | Variables | Functions | Classes | Files |
|---|---|---|---|---|
| Python | snake_case | snake_case | PascalCase | snake_case.py |
| JavaScript/TypeScript | camelCase | camelCase | PascalCase | kebab-case.ts |
| Java | camelCase | camelCase | PascalCase | PascalCase.java |
| C# | camelCase | PascalCase | PascalCase | PascalCase.cs |
| Go | camelCase (unexported), PascalCase (exported) | Same as variables | PascalCase | snake_case.go |
| Ruby | snake_case | snake_case | PascalCase | snake_case.rb |
| PHP | camelCase | camelCase | PascalCase | PascalCase.php |
| Rust | snake_case | snake_case | PascalCase | snake_case.rs |
Naming Semantics
Names communicate intent through consistent semantic patterns. A function prefixed with get returns a value without side effects; a function prefixed with set modifies state. Violating these semantics forces developers to read implementation to understand behaviour.
| Prefix/suffix | Meaning | Returns | Side effects | Example |
|---|---|---|---|---|
get | Retrieve existing value | Value or null/undefined | None | getUser(id) |
find | Search with possible failure | Value, null, or collection | None | findByEmail(email) |
fetch | Retrieve from external source | Value or throws | Network I/O | fetchUserProfile(id) |
create | Instantiate new entity | New instance | Persistence | createUser(data) |
update | Modify existing entity | Updated instance or void | Persistence | updateUser(id, data) |
delete | Remove entity | Boolean or void | Persistence | deleteUser(id) |
is, has, can | Boolean check | Boolean | None | isValid(), hasPermission() |
to | Convert to different type | Converted value | None | toString(), toJSON() |
parse | Extract structured data | Parsed value or throws | None | parseDate(string) |
validate | Check correctness | Boolean or errors | None | validateEmail(input) |
handle | Process event or request | Varies | Varies | handleSubmit(event) |
on | Event listener | Void | Registers callback | onClick(callback) |
_ prefix | Private/internal | N/A | N/A | _internalHelper() |
Impl suffix | Implementation class | N/A | N/A | UserRepositoryImpl |
Base prefix | Abstract parent | N/A | N/A | BaseController |
Abbreviations
Abbreviations reduce readability for developers unfamiliar with domain-specific shorthand. The table below lists permitted abbreviations; all others must be written in full.
| Abbreviation | Full form | Context |
|---|---|---|
id | identifier | Universal |
db | database | Universal |
api | application programming interface | Universal |
url | uniform resource locator | Universal |
uri | uniform resource identifier | Universal |
http | hypertext transfer protocol | Universal |
json | JavaScript object notation | Universal |
xml | extensible markup language | Universal |
html | hypertext markup language | Universal |
css | cascading style sheets | Universal |
sql | structured query language | Universal |
err | error | Variable names only |
ctx | context | Variable names only |
req | request | Variable names only |
res | response | Variable names only |
config | configuration | Universal |
env | environment | Universal |
auth | authentication/authorisation | Universal |
admin | administrator | Universal |
info | information | Log levels only |
init | initialise | Function names only |
util | utility | Module names only |
src | source | Directory names only |
dist | distribution | Directory names only |
tmp | temporary | Variable/directory names |
max | maximum | Universal |
min | minimum | Universal |
num | number | Variable names only |
prev | previous | Variable names only |
curr | current | Variable names only |
Domain-specific abbreviations (beneficiary ID as bid, monitoring and evaluation as me) require definition in a project glossary file at the repository root.
Project Structure
Project structure conventions ensure developers locate files predictably across repositories. The structures below represent minimum required directories; projects add subdirectories as complexity grows.
Web Application Structure
project-root/|+-- src/| +-- components/ # Reusable UI components| +-- pages/ # Route-level components| +-- services/ # API and external service clients| +-- hooks/ # Custom hooks (React) or composables (Vue)| +-- utils/ # Pure utility functions| +-- types/ # Type definitions| +-- styles/ # Global styles, variables| +-- assets/ # Static assets (images, fonts)| +-- constants/ # Application constants| +-- contexts/ # State management contexts| +-- config/ # Configuration loaders| +-- index.ts # Application entry point|+-- tests/| +-- unit/ # Unit tests mirroring src structure| +-- integration/ # Integration tests| +-- e2e/ # End-to-end tests| +-- fixtures/ # Test data|+-- public/ # Static files served directly+-- docs/ # Project documentation+-- scripts/ # Build and utility scripts+-- .github/ # GitHub Actions workflows+-- config/ # Build tool configurationAPI Service Structure
project-root/|+-- src/| +-- controllers/ # Request handlers| +-- services/ # Business logic| +-- repositories/ # Data access| +-- models/ # Domain entities| +-- middleware/ # Request/response middleware| +-- validators/ # Input validation schemas| +-- routes/ # Route definitions| +-- utils/ # Utility functions| +-- types/ # Type definitions| +-- config/ # Configuration management| +-- errors/ # Custom error classes| +-- index.ts # Application entry point|+-- tests/| +-- unit/| +-- integration/| +-- fixtures/|+-- migrations/ # Database migrations+-- seeds/ # Database seed data+-- docs/| +-- api/ # API documentation (OpenAPI)+-- scripts/Data Pipeline Structure
project-root/|+-- src/| +-- extractors/ # Data source connectors| +-- transformers/ # Data transformation logic| +-- loaders/ # Data destination writers| +-- validators/ # Data quality checks| +-- models/ # Data models and schemas| +-- utils/| +-- config/|+-- pipelines/ # Pipeline definitions (DAGs)+-- tests/+-- data/| +-- raw/ # Unprocessed input data| +-- processed/ # Transformed output data| +-- schemas/ # Schema definitions+-- docs/+-- scripts/Mobile Application Structure
project-root/|+-- src/| +-- screens/ # Screen-level components| +-- components/ # Reusable UI components| +-- navigation/ # Navigation configuration| +-- services/ # API and device services| +-- store/ # State management| +-- hooks/ # Custom hooks| +-- utils/| +-- types/| +-- assets/| +-- constants/| +-- config/|+-- tests/+-- ios/ # iOS native code+-- android/ # Android native code+-- docs/+-- scripts/File Placement Rules
When uncertainty exists about file placement, the following rules resolve ambiguity:
Files belong in utils/ when they contain pure functions with no dependencies on application state, external services, or framework features. A function that formats dates belongs in utils/; a function that fetches user preferences from an API belongs in services/.
Files belong in services/ when they encapsulate interaction with external systems: APIs, databases, file systems, or third-party services. Services abstract the details of external communication from business logic.
Files belong in models/ or types/ when they define data structures without behaviour. TypeScript interfaces, database entity definitions, and API response shapes belong here. Classes with methods that operate on data belong in services/ or domain-specific directories.
Files belong in config/ when they read environment variables, load configuration files, or define environment-specific settings. Configuration files export typed configuration objects, not raw environment variable access scattered throughout the codebase.
Code Organisation
Code organisation principles govern how logic distributes across files and modules. These principles apply regardless of programming paradigm or framework.
Separation of Concerns
Each file addresses a single responsibility. A file that handles HTTP requests does not also format dates, validate email addresses, and generate PDF reports. When a file requires a descriptive comment explaining what it does, the file likely contains multiple concerns that belong in separate files.
The responsibility test: describe what a file does in one sentence without using “and”. If the sentence requires “and”, the file requires splitting. “UserController handles HTTP requests for user endpoints” passes. “UserController handles HTTP requests and validates input and formats responses” fails.
Module Boundaries
Modules expose public interfaces and hide implementation details. Code outside a module interacts only with exported functions, classes, and types. Internal helpers remain unexported.
+------------------------------------------------------------------+| MODULE BOUNDARY |+------------------------------------------------------------------+| || EXPORTED (Public API) INTERNAL (Implementation) || +------------------------+ +------------------------+ || | | | | || | createUser() | +--->| validateUserData() | || | updateUser() | | | hashPassword() | || | deleteUser() +----+ | generateUserId() | || | UserService class | | | formatUserResponse() | || | User type | +--->| normaliseEmail() | || | | | | || +------------------------+ +------------------------+ || || Consumers import only Internal functions not exported; || from public API changes don't break consumers || |+------------------------------------------------------------------+Index files (index.ts, init.py, mod.rs) define module boundaries by re-exporting public interfaces. Importing directly from internal files bypasses the module boundary and creates coupling to implementation details.
Dependency Direction
Dependencies flow inward toward core business logic. Outer layers (HTTP handlers, database repositories, external service clients) depend on inner layers (domain models, business rules). Inner layers never import from outer layers.
+------------------------------------------------------------------+| || +------------------------------------------------------------+ || | INFRASTRUCTURE LAYER | || | HTTP handlers, database clients, external APIs, CLI | || +-----------------------------+------------------------------+ || | || v depends on || +-----------------------------+------------------------------+ || | APPLICATION LAYER | || | Use cases, application services, DTOs | || +-----------------------------+------------------------------+ || | || v depends on || +-----------------------------+------------------------------+ || | DOMAIN LAYER | || | Entities, value objects, domain services, business rules | || +------------------------------------------------------------+ || || Dependencies point inward. Domain layer has no external imports.|| Infrastructure layer imports from application and domain. || |+------------------------------------------------------------------+This dependency direction enables testing domain and application layers without infrastructure dependencies. Tests substitute mock implementations for database clients and HTTP services.
File Size Guidelines
| Metric | Target | Maximum | Action when exceeded |
|---|---|---|---|
| Lines per file | Under 300 | 500 | Extract modules |
| Functions per file | Under 10 | 15 | Extract related functions to new file |
| Parameters per function | Under 4 | 6 | Use parameter object |
| Nesting depth | Under 3 | 4 | Extract nested logic to functions |
| Cyclomatic complexity | Under 10 | 15 | Simplify conditionals, extract functions |
These limits indicate when refactoring improves maintainability. Exceeding limits occasionally for legitimate reasons is acceptable; consistently exceeding limits signals structural problems.
Error Handling
Error handling conventions establish consistent patterns for raising, propagating, and recovering from errors. Inconsistent error handling produces applications that fail unpredictably and provide unhelpful diagnostic information.
Error Classification
Errors divide into categories that determine handling strategy:
Operational errors arise from external conditions: network failures, invalid user input, missing files, database connection timeouts. Applications anticipate and handle operational errors gracefully by retrying, falling back to cached data, or returning informative error responses.
Programming errors arise from bugs: null pointer dereferences, index out of bounds, type mismatches. Applications do not handle programming errors at runtime; instead, they fail fast with full diagnostic information to enable debugging. Attempting to recover from programming errors masks bugs.
Resource exhaustion errors arise when systems exceed capacity: out of memory, too many open files, thread pool exhaustion. Applications handle these by shedding load, queuing requests, or degrading gracefully.
Error Propagation
Errors propagate up the call stack until handled. Each layer adds context relevant to that layer without obscuring the original error.
+--------------------------------------------------------------------+| ERROR PROPAGATION |+--------------------------------------------------------------------+| || Database Layer "Connection refused to db.example.org:5432" || | || v || Repository Layer "Failed to fetch user: [db error]" || | || v || Service Layer "User lookup failed for id 12345: [repo]" || | || v || Controller Layer "Request failed: [service error]" || | || v || HTTP Response { status: 500, message: "Internal error", || requestId: "abc-123" } || || Internal error chain preserved in logs; external response || contains only safe information and correlation ID. || |+--------------------------------------------------------------------+Error messages intended for logs include full technical detail. Error messages returned to users exclude internal details (file paths, database queries, stack traces) that could expose system architecture to attackers or confuse non-technical users.
Error Response Structure
API error responses use consistent structure across all endpoints:
{ "error": { "code": "VALIDATION_ERROR", "message": "The request contains invalid data", "details": [ { "field": "email", "message": "Must be a valid email address", "value": "not-an-email" } ], "requestId": "req-abc-123-def" }}| Field | Required | Purpose |
|---|---|---|
error.code | Yes | Machine-readable error category |
error.message | Yes | Human-readable summary |
error.details | No | Field-level errors for validation failures |
error.requestId | Yes | Correlation ID for log lookup |
Error Codes
Error codes follow a consistent naming scheme that indicates error category without requiring documentation lookup.
| Prefix | Category | HTTP status | Example |
|---|---|---|---|
VALIDATION_ | Input validation failure | 400 | VALIDATION_REQUIRED_FIELD |
AUTH_ | Authentication failure | 401 | AUTH_TOKEN_EXPIRED |
FORBIDDEN_ | Authorisation failure | 403 | FORBIDDEN_INSUFFICIENT_ROLE |
NOT_FOUND_ | Resource not found | 404 | NOT_FOUND_USER |
CONFLICT_ | State conflict | 409 | CONFLICT_DUPLICATE_EMAIL |
RATE_LIMIT_ | Rate limiting | 429 | RATE_LIMIT_EXCEEDED |
INTERNAL_ | Internal error | 500 | INTERNAL_DATABASE_ERROR |
EXTERNAL_ | External service failure | 502 | EXTERNAL_PAYMENT_PROVIDER |
UNAVAILABLE_ | Service unavailable | 503 | UNAVAILABLE_MAINTENANCE |
Exception Handling Patterns
Try-catch blocks wrap the smallest possible code section. Broad exception handlers obscure the source of errors and catch exceptions that should propagate.
AVOID: Broad exception handling
try { validateInput(data); user = fetchUser(id); result = processPayment(user, amount); sendConfirmation(user, result); return result;} catch (error) { log.error("Something failed", error); throw new InternalError();}
PREFER: Targeted exception handling
validateInput(data); // Throws ValidationError, let it propagate
let user;try { user = fetchUser(id);} catch (error) { throw new UserLookupError(id, error);}
let result;try { result = processPayment(user, amount);} catch (error) { throw new PaymentError(user.id, amount, error);}
sendConfirmation(user, result); // Fire and forget, log failuresreturn result;Retry Handling
Retries apply only to transient failures: network timeouts, temporary service unavailability, database connection drops. Retrying permanent failures (authentication failures, validation errors, not found responses) wastes resources and delays error reporting.
| Failure type | Retry | Backoff | Max attempts |
|---|---|---|---|
| Network timeout | Yes | Exponential | 3 |
| Connection refused | Yes | Exponential | 3 |
| HTTP 429 (rate limit) | Yes | Per Retry-After header | 3 |
| HTTP 500 (server error) | Yes | Exponential | 3 |
| HTTP 502/503/504 | Yes | Exponential | 3 |
| HTTP 400 (client error) | No | N/A | 1 |
| HTTP 401/403 | No | N/A | 1 |
| HTTP 404 | No | N/A | 1 |
| Validation failure | No | N/A | 1 |
| Authentication failure | No | N/A | 1 |
Exponential backoff starts at 100ms, doubles each attempt, and adds random jitter of 0-100ms to prevent thundering herd problems when multiple clients retry simultaneously.
Logging Standards
Logging provides operational visibility into application behaviour. Effective logs enable debugging production issues, monitoring system health, and auditing security-relevant events.
Log Levels
Log levels indicate severity and filter log output by environment. Production environments typically emit INFO and above; development environments emit DEBUG and above.
| Level | Numeric | Purpose | Retention | Example |
|---|---|---|---|---|
TRACE | 10 | Detailed debugging | Development only | Function entry/exit, variable values |
DEBUG | 20 | Diagnostic information | Development, staging | Query parameters, intermediate results |
INFO | 30 | Normal operations | All environments | Request handled, job completed |
WARN | 40 | Potential problems | All environments | Deprecated feature used, retry occurred |
ERROR | 50 | Failures requiring attention | All environments | Request failed, integration error |
FATAL | 60 | Unrecoverable failures | All environments | Application cannot start |
Log Format
Structured logging outputs JSON objects that log aggregation systems parse and index. Unstructured text logs require regex parsing and provide inconsistent searchability.
{ "timestamp": "2024-11-16T14:30:00.123Z", "level": "INFO", "message": "User authentication successful", "service": "auth-service", "version": "1.2.3", "environment": "production", "requestId": "req-abc-123", "traceId": "trace-xyz-789", "userId": "user-456", "duration_ms": 45, "metadata": { "authMethod": "oauth2", "provider": "google" }}| Field | Required | Purpose |
|---|---|---|
timestamp | Yes | ISO 8601 format with milliseconds, UTC timezone |
level | Yes | Log level as string |
message | Yes | Human-readable event description |
service | Yes | Service name for multi-service environments |
version | Yes | Application version |
environment | Yes | Deployment environment |
requestId | Conditional | Present for request-scoped logs |
traceId | Conditional | Present when distributed tracing enabled |
userId | Conditional | Present for authenticated requests |
duration_ms | Conditional | Present for timed operations |
metadata | No | Additional structured data |
What to Log
Logging captures events that support operations, debugging, and security auditing. Not every action requires logging; excessive logging degrades performance and obscures important events in noise.
| Category | Log level | Required fields | Example events |
|---|---|---|---|
| HTTP requests | INFO | method, path, status, duration_ms, requestId | All incoming requests |
| Authentication | INFO | userId, authMethod, success | Login, logout, token refresh |
| Authorisation | WARN | userId, resource, action, denied | Permission denied events |
| Data access | DEBUG | entity, operation, recordCount | Database queries in development |
| External calls | INFO | service, endpoint, status, duration_ms | API calls to external services |
| Business events | INFO | eventType, entityId, metadata | Domain-significant state changes |
| Errors | ERROR | errorCode, message, stack, context | All handled exceptions |
| Performance | WARN | operation, duration_ms, threshold | Operations exceeding thresholds |
| Configuration | INFO | setting, value (non-sensitive) | Configuration loaded at startup |
What Not to Log
Certain data never appears in logs regardless of level. Logging sensitive data creates compliance violations, security vulnerabilities, and data protection issues.
| Data type | Reason | Alternative |
|---|---|---|
| Passwords | Security | Log authentication events without credentials |
| API keys/tokens | Security | Log presence of token, not value |
| Personal identifiers (PII) | Privacy/GDPR | Log anonymised or hashed identifiers |
| Credit card numbers | PCI-DSS | Log masked value (last 4 digits) |
| Health information | HIPAA/privacy | Log event type without details |
| Full request bodies | May contain sensitive data | Log specific safe fields |
| Full response bodies | May contain sensitive data | Log status and size |
| Database credentials | Security | Log connection events without credentials |
| Internal IP addresses | Security | Log service names instead |
| File system paths | Security | Log relative paths or identifiers |
Log Correlation
Distributed systems generate logs across multiple services. Correlation identifiers link related log entries across service boundaries.
+------------------------------------------------------------------+| REQUEST FLOW |+------------------------------------------------------------------+| || Client Request || | || | X-Request-ID: req-abc-123 || v || +------------+ +------------+ +------------+ || | API | | User | | Payment | || | Gateway +---->| Service +---->| Service | || | | | | | | || | requestId: | | requestId: | | requestId: | || | req-abc-123| | req-abc-123| | req-abc-123| || +------------+ +------------+ +------------+ || || All services propagate requestId, enabling log correlation || Query: requestId="req-abc-123" returns full request trace || |+------------------------------------------------------------------+Request identifiers generate at the entry point (API gateway, load balancer) and propagate through all downstream calls via HTTP headers. Each service extracts the identifier and includes it in all log entries for that request.
Dependency Management
Dependency management governs how projects declare, resolve, and update external packages. Poor dependency management produces build failures, security vulnerabilities, and version conflicts.
Version Pinning
Production applications pin exact dependency versions to ensure reproducible builds. A Cargo.lock, package-lock.json, Pipfile.lock, or equivalent lock file commits to version control and determines deployed versions.
| Specification | Meaning | Use case |
|---|---|---|
1.2.3 | Exact version | Production dependencies |
^1.2.3 | Compatible updates (1.x.x) | Development dependencies with semver trust |
~1.2.3 | Patch updates only (1.2.x) | Production with controlled updates |
>=1.2.3 | Minimum version | Avoid; produces unpredictable builds |
* | Any version | Never use |
Lock files pin the full dependency tree including transitive dependencies. Manifest files (package.json, Cargo.toml, requirements.txt) specify direct dependencies with version constraints; lock files record resolved versions.
Update Frequency
Dependencies require regular updates to incorporate security patches and bug fixes. Infrequent updates create painful large-version jumps; excessive updates consume development time and risk regressions.
| Dependency category | Update frequency | Automation |
|---|---|---|
| Security patches | Within 72 hours of disclosure | Automated PRs, expedited review |
| Framework/runtime | Monthly evaluation, quarterly application | Manual with testing |
| Build tools | Quarterly | Manual |
| Development dependencies | Monthly | Automated PRs |
| Transitive dependencies | Via direct dependency updates | Lock file regeneration |
Automated dependency update tools (Dependabot, Renovate) create pull requests when new versions publish. These PRs require the same review and testing as code changes. Merging without review introduces untested code into the build.
Dependency Selection Criteria
Adding dependencies introduces maintenance burden, security surface, and supply chain risk. Each dependency requires ongoing attention: security monitoring, compatibility testing, eventual replacement when abandoned.
Before adding a dependency, evaluate:
| Criterion | Threshold | Verification method |
|---|---|---|
| Maintenance activity | Commits within 6 months | Repository activity |
| Open issues | Under 100 unaddressed | Issue tracker |
| Security advisories | None unpatched | Security database |
| Licence compatibility | OSI-approved, compatible | Licence file |
| Transitive dependencies | Under 20 | Dependency tree analysis |
| Bundle size (frontend) | Under 50KB gzipped | Bundle analyser |
| Download count | Over 1000 weekly | Package registry |
A function achievable in 50 lines of code rarely justifies a dependency with 200 transitive packages. Evaluate whether implementing functionality directly produces less total complexity than adopting a dependency.
Licence Compliance
Dependencies carry licence obligations that flow to the consuming project. Incompatible licences create legal risk and distribution restrictions.
| Licence category | Obligations | Compatibility |
|---|---|---|
| MIT, BSD, ISC | Attribution | Compatible with all |
| Apache 2.0 | Attribution, patent grant | Compatible with all |
| MPL 2.0 | File-level copyleft | Compatible; modified files share-alike |
| LGPL | Library copyleft | Compatible; dynamic linking preserves proprietary |
| GPL | Strong copyleft | Infectious; entire work becomes GPL |
| AGPL | Network copyleft | Infectious; network use triggers obligations |
| Proprietary | Per agreement | Requires legal review |
| No licence | All rights reserved | Not usable |
Automated licence scanning tools (FOSSA, licensed, license-checker) identify dependencies with incompatible or missing licences. Run licence checks in CI pipelines to catch violations before merge.
Environment Configuration
Environment configuration separates deployment-specific settings from code. The same codebase deploys to development, staging, and production with different database connections, API endpoints, and feature flags.
Configuration Sources
Configuration loads from multiple sources with clear precedence. Later sources override earlier ones, enabling environment-specific overrides without modifying base configuration.
+---------------------------------------------------------------------+| CONFIGURATION PRECEDENCE |+---------------------------------------------------------------------+| || LOWEST PRIORITY HIGHEST PRIORITY || || +--------------+ +---------------+ +-------------+ +----------+ || | Default | | Config file | | Environment | | CLI | || | values in +->| (config.yaml, +->| variables +->| flags | || | code | | .env) | | (DATABASE_ | | (--port) | || | | | | | URL, etc.) | | | || +--------------+ +---------------+ +-------------+ +----------+ || || Each level overrides previous. Environment variables override || file-based config; CLI flags override everything. || |+---------------------------------------------------------------------+Environment Variable Naming
Environment variables use SCREAMING_SNAKE_CASE with prefixes that indicate category:
| Prefix | Category | Example |
|---|---|---|
APP_ | Application settings | APP_PORT, APP_LOG_LEVEL |
DB_ | Database configuration | DB_HOST, DB_PASSWORD |
REDIS_ | Redis/cache configuration | REDIS_URL |
API_ | External API configuration | API_PAYMENT_KEY |
AWS_, AZURE_, GCP_ | Cloud provider | AWS_REGION |
SMTP_ | Email configuration | SMTP_HOST |
FEATURE_ | Feature flags | FEATURE_NEW_DASHBOARD |
LOG_ | Logging configuration | LOG_LEVEL, LOG_FORMAT |
Applications never read process.env directly throughout the codebase. A configuration module reads environment variables at startup, validates required values exist, and exports typed configuration objects. This centralises configuration access and fails fast on missing required configuration.
Configuration Validation
Applications validate configuration at startup and fail immediately when required configuration is missing or invalid. Discovering missing configuration during request handling produces confusing errors and inconsistent behaviour.
+------------------------------------------------------------------+| CONFIGURATION VALIDATION |+------------------------------------------------------------------+| || Application Start || | || v || +------------------+ || | Load config from | || | all sources | || +--------+---------+ || | || v || +--------+---------+ +------------------+ || | Validate required|---->| Missing required |---> EXIT with || | fields present | NO | configuration | clear error || +--------+---------+ +------------------+ || | YES || v || +--------+---------+ +------------------+ || | Validate format |---->| Invalid format |---> EXIT with || | (URLs, numbers) | NO | | clear error || +--------+---------+ +------------------+ || | YES || v || +--------+---------+ || | Export typed | || | config object | || +--------+---------+ || | || v || Application Ready || |+------------------------------------------------------------------+Secrets Management
Secrets (database passwords, API keys, encryption keys) never appear in configuration files committed to version control. Environment variables or dedicated secrets management systems provide secrets at runtime.
| Secret type | Storage | Rotation frequency |
|---|---|---|
| Database credentials | Secrets manager | 90 days |
| API keys (internal) | Secrets manager | 90 days |
| API keys (external) | Secrets manager | Per provider policy |
| Encryption keys | Key management service | Annually |
| TLS certificates | Certificate manager | Before expiry |
| OAuth client secrets | Secrets manager | Annually |
Development environments use .env files excluded from version control via .gitignore. A .env.example file with placeholder values documents required configuration without exposing actual secrets.
For detailed secrets management procedures, see Secrets Management.