Skip to main content

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 typeConventionLength limitExamples
Variable (local)camelCase or snake_case per language30 charactersuserCount, user_count
ConstantSCREAMING_SNAKE_CASE40 charactersMAX_RETRY_COUNT, DEFAULT_TIMEOUT_MS
Function/methodcamelCase or snake_case per language40 characterscalculateTotal, calculate_total
Class/typePascalCase40 charactersUserRepository, PaymentService
InterfacePascalCase, no prefix40 charactersSerializable, EventHandler
EnumPascalCase for type, UPPER_CASE for values30 charactersStatus.PENDING, Status.APPROVED
Filekebab-case or snake_case per ecosystem50 charactersuser-service.js, user_service.py
Directorykebab-case30 charactersdata-access, api-handlers
Database tablesnake_case, plural30 charactersusers, payment_records
Database columnsnake_case30 characterscreated_at, user_id
API endpointkebab-case, plural nouns50 characters/api/v1/user-accounts
Environment variableSCREAMING_SNAKE_CASE50 charactersDATABASE_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.

LanguageVariablesFunctionsClassesFiles
Pythonsnake_casesnake_casePascalCasesnake_case.py
JavaScript/TypeScriptcamelCasecamelCasePascalCasekebab-case.ts
JavacamelCasecamelCasePascalCasePascalCase.java
C#camelCasePascalCasePascalCasePascalCase.cs
GocamelCase (unexported), PascalCase (exported)Same as variablesPascalCasesnake_case.go
Rubysnake_casesnake_casePascalCasesnake_case.rb
PHPcamelCasecamelCasePascalCasePascalCase.php
Rustsnake_casesnake_casePascalCasesnake_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/suffixMeaningReturnsSide effectsExample
getRetrieve existing valueValue or null/undefinedNonegetUser(id)
findSearch with possible failureValue, null, or collectionNonefindByEmail(email)
fetchRetrieve from external sourceValue or throwsNetwork I/OfetchUserProfile(id)
createInstantiate new entityNew instancePersistencecreateUser(data)
updateModify existing entityUpdated instance or voidPersistenceupdateUser(id, data)
deleteRemove entityBoolean or voidPersistencedeleteUser(id)
is, has, canBoolean checkBooleanNoneisValid(), hasPermission()
toConvert to different typeConverted valueNonetoString(), toJSON()
parseExtract structured dataParsed value or throwsNoneparseDate(string)
validateCheck correctnessBoolean or errorsNonevalidateEmail(input)
handleProcess event or requestVariesVarieshandleSubmit(event)
onEvent listenerVoidRegisters callbackonClick(callback)
_ prefixPrivate/internalN/AN/A_internalHelper()
Impl suffixImplementation classN/AN/AUserRepositoryImpl
Base prefixAbstract parentN/AN/ABaseController

Abbreviations

Abbreviations reduce readability for developers unfamiliar with domain-specific shorthand. The table below lists permitted abbreviations; all others must be written in full.

AbbreviationFull formContext
ididentifierUniversal
dbdatabaseUniversal
apiapplication programming interfaceUniversal
urluniform resource locatorUniversal
uriuniform resource identifierUniversal
httphypertext transfer protocolUniversal
jsonJavaScript object notationUniversal
xmlextensible markup languageUniversal
htmlhypertext markup languageUniversal
csscascading style sheetsUniversal
sqlstructured query languageUniversal
errerrorVariable names only
ctxcontextVariable names only
reqrequestVariable names only
resresponseVariable names only
configconfigurationUniversal
envenvironmentUniversal
authauthentication/authorisationUniversal
adminadministratorUniversal
infoinformationLog levels only
initinitialiseFunction names only
utilutilityModule names only
srcsourceDirectory names only
distdistributionDirectory names only
tmptemporaryVariable/directory names
maxmaximumUniversal
minminimumUniversal
numnumberVariable names only
prevpreviousVariable names only
currcurrentVariable 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 configuration

API 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

MetricTargetMaximumAction when exceeded
Lines per fileUnder 300500Extract modules
Functions per fileUnder 1015Extract related functions to new file
Parameters per functionUnder 46Use parameter object
Nesting depthUnder 34Extract nested logic to functions
Cyclomatic complexityUnder 1015Simplify 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"
}
}
FieldRequiredPurpose
error.codeYesMachine-readable error category
error.messageYesHuman-readable summary
error.detailsNoField-level errors for validation failures
error.requestIdYesCorrelation ID for log lookup

Error Codes

Error codes follow a consistent naming scheme that indicates error category without requiring documentation lookup.

PrefixCategoryHTTP statusExample
VALIDATION_Input validation failure400VALIDATION_REQUIRED_FIELD
AUTH_Authentication failure401AUTH_TOKEN_EXPIRED
FORBIDDEN_Authorisation failure403FORBIDDEN_INSUFFICIENT_ROLE
NOT_FOUND_Resource not found404NOT_FOUND_USER
CONFLICT_State conflict409CONFLICT_DUPLICATE_EMAIL
RATE_LIMIT_Rate limiting429RATE_LIMIT_EXCEEDED
INTERNAL_Internal error500INTERNAL_DATABASE_ERROR
EXTERNAL_External service failure502EXTERNAL_PAYMENT_PROVIDER
UNAVAILABLE_Service unavailable503UNAVAILABLE_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 failures
return 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 typeRetryBackoffMax attempts
Network timeoutYesExponential3
Connection refusedYesExponential3
HTTP 429 (rate limit)YesPer Retry-After header3
HTTP 500 (server error)YesExponential3
HTTP 502/503/504YesExponential3
HTTP 400 (client error)NoN/A1
HTTP 401/403NoN/A1
HTTP 404NoN/A1
Validation failureNoN/A1
Authentication failureNoN/A1

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.

LevelNumericPurposeRetentionExample
TRACE10Detailed debuggingDevelopment onlyFunction entry/exit, variable values
DEBUG20Diagnostic informationDevelopment, stagingQuery parameters, intermediate results
INFO30Normal operationsAll environmentsRequest handled, job completed
WARN40Potential problemsAll environmentsDeprecated feature used, retry occurred
ERROR50Failures requiring attentionAll environmentsRequest failed, integration error
FATAL60Unrecoverable failuresAll environmentsApplication 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"
}
}
FieldRequiredPurpose
timestampYesISO 8601 format with milliseconds, UTC timezone
levelYesLog level as string
messageYesHuman-readable event description
serviceYesService name for multi-service environments
versionYesApplication version
environmentYesDeployment environment
requestIdConditionalPresent for request-scoped logs
traceIdConditionalPresent when distributed tracing enabled
userIdConditionalPresent for authenticated requests
duration_msConditionalPresent for timed operations
metadataNoAdditional 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.

CategoryLog levelRequired fieldsExample events
HTTP requestsINFOmethod, path, status, duration_ms, requestIdAll incoming requests
AuthenticationINFOuserId, authMethod, successLogin, logout, token refresh
AuthorisationWARNuserId, resource, action, deniedPermission denied events
Data accessDEBUGentity, operation, recordCountDatabase queries in development
External callsINFOservice, endpoint, status, duration_msAPI calls to external services
Business eventsINFOeventType, entityId, metadataDomain-significant state changes
ErrorsERRORerrorCode, message, stack, contextAll handled exceptions
PerformanceWARNoperation, duration_ms, thresholdOperations exceeding thresholds
ConfigurationINFOsetting, 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 typeReasonAlternative
PasswordsSecurityLog authentication events without credentials
API keys/tokensSecurityLog presence of token, not value
Personal identifiers (PII)Privacy/GDPRLog anonymised or hashed identifiers
Credit card numbersPCI-DSSLog masked value (last 4 digits)
Health informationHIPAA/privacyLog event type without details
Full request bodiesMay contain sensitive dataLog specific safe fields
Full response bodiesMay contain sensitive dataLog status and size
Database credentialsSecurityLog connection events without credentials
Internal IP addressesSecurityLog service names instead
File system pathsSecurityLog 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.

SpecificationMeaningUse case
1.2.3Exact versionProduction dependencies
^1.2.3Compatible updates (1.x.x)Development dependencies with semver trust
~1.2.3Patch updates only (1.2.x)Production with controlled updates
>=1.2.3Minimum versionAvoid; produces unpredictable builds
*Any versionNever 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 categoryUpdate frequencyAutomation
Security patchesWithin 72 hours of disclosureAutomated PRs, expedited review
Framework/runtimeMonthly evaluation, quarterly applicationManual with testing
Build toolsQuarterlyManual
Development dependenciesMonthlyAutomated PRs
Transitive dependenciesVia direct dependency updatesLock 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:

CriterionThresholdVerification method
Maintenance activityCommits within 6 monthsRepository activity
Open issuesUnder 100 unaddressedIssue tracker
Security advisoriesNone unpatchedSecurity database
Licence compatibilityOSI-approved, compatibleLicence file
Transitive dependenciesUnder 20Dependency tree analysis
Bundle size (frontend)Under 50KB gzippedBundle analyser
Download countOver 1000 weeklyPackage 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 categoryObligationsCompatibility
MIT, BSD, ISCAttributionCompatible with all
Apache 2.0Attribution, patent grantCompatible with all
MPL 2.0File-level copyleftCompatible; modified files share-alike
LGPLLibrary copyleftCompatible; dynamic linking preserves proprietary
GPLStrong copyleftInfectious; entire work becomes GPL
AGPLNetwork copyleftInfectious; network use triggers obligations
ProprietaryPer agreementRequires legal review
No licenceAll rights reservedNot 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:

PrefixCategoryExample
APP_Application settingsAPP_PORT, APP_LOG_LEVEL
DB_Database configurationDB_HOST, DB_PASSWORD
REDIS_Redis/cache configurationREDIS_URL
API_External API configurationAPI_PAYMENT_KEY
AWS_, AZURE_, GCP_Cloud providerAWS_REGION
SMTP_Email configurationSMTP_HOST
FEATURE_Feature flagsFEATURE_NEW_DASHBOARD
LOG_Logging configurationLOG_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 typeStorageRotation frequency
Database credentialsSecrets manager90 days
API keys (internal)Secrets manager90 days
API keys (external)Secrets managerPer provider policy
Encryption keysKey management serviceAnnually
TLS certificatesCertificate managerBefore expiry
OAuth client secretsSecrets managerAnnually

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.

See Also