Singleton Pattern for App Configuration: Single Source of Truth Guide
How to use Singleton for app config the right way, and when to choose a different pattern.
What’s a Singleton?
A Singleton pattern ensures only one instance of a class exists in a process, and provides a shared access point to that instance.
For app configuration, that usually means:
- Load configuration once
- Reuse the same in-memory config object everywhere
- Avoid duplicate config-loading logic across modules
Important scope note: a Singleton gives you one instance per process, not one instance across multiple services or machines.
Use it when:
- You need shared access to a process-wide resource (config, logging, metrics)
- You want one-time initialization
- You want consistent reads from one in-memory object
Singleton and Single Source of Truth (SSOT)
Single Source of Truth means the same value should come from one authoritative place.
In app config terms:
- The config file (or config provider) is the source of truth at load time
- The Singleton config store is the source of truth at runtime inside the process
This is where Singleton can be a good architectural fit: it helps enforce one runtime authority for configuration values.
Real-World Example (TypeScript)
Let’s use a lightweight Node/TypeScript example with an AppConfigStore class to make the role explicit.
type AppConfig = {
apiBaseUrl: string;
requestTimeoutMs: number;
backupDirectory: string;
};
class AppConfigStore {
private static instance: AppConfigStore;
private readonly config: AppConfig;
private constructor() {
this.config = {
apiBaseUrl: process.env.API_BASE_URL ?? "https://api.example.com",
requestTimeoutMs: Number(process.env.REQUEST_TIMEOUT_MS ?? 5000),
backupDirectory: process.env.BACKUP_DIR ?? "./backups",
};
}
static getInstance(): AppConfigStore {
if (!AppConfigStore.instance) {
AppConfigStore.instance = new AppConfigStore();
}
return AppConfigStore.instance;
}
get(): AppConfig {
return this.config;
}
}
export const appConfig = AppConfigStore.getInstance();
The first call builds the store; every later call reuses the same in-memory config object.
What Does the Config Store Do?
A practical config store often handles:
- Resolving config file paths
- Loading defaults + user overrides
- Validating values and types
- Exposing typed getters for app modules
Example usage:
import { appConfig } from "./app-config";
const { apiBaseUrl, requestTimeoutMs } = appConfig.get();
console.log("API:", apiBaseUrl, "timeout:", requestTimeoutMs);
This pattern works best when config reads are frequent and writes are controlled.
Design Guardrails
If you use a Singleton for config, keep these constraints in mind:
- Prefer immutable config after startup when possible
- Keep write paths explicit and minimal
- Avoid mixing unrelated responsibilities into the same class
- Provide interfaces/adapters for testing instead of hard-coding global access everywhere
Singleton should enforce consistency, not become a hidden global state bucket.
When to Skip Singleton
A Singleton may not be the right fit if:
- You need multiple active config contexts (multi-tenant, per-workspace, per-request)
- You need isolated unit tests with easy replacement/mocking
- You run distributed systems where true SSOT belongs in a central service/store
- You expect frequent runtime hot-reloads from different sources
In those cases, dependency injection and explicit config providers are usually better.
Final Thoughts
For local application configuration, Singleton and SSOT can work well together:
- Singleton gives one runtime config instance
- SSOT gives one authoritative path for where values come from
Use Singleton intentionally, keep config behavior explicit, and avoid uncontrolled mutable global state. That gives you the simplicity benefit without the usual architecture debt.